Skip to content

Webhook replay

import { Aside } from ‘@astrojs/starlight/components’

Webhook replay lets you re-deliver a previously received webhook to any of your registered deployments. It’s useful when your target was down, you had a bug that’s now fixed, or you want to test a different branch against real production data.

How it works

When Hookman receives a webhook, it stores the full request payload (headers + body) in Cloudflare KV alongside the log entry. Replay reads that stored payload and re-delivers it as a new POST request.

Replay delivery is asynchronous — it goes through a queue and runs in the background. You get a 202 Accepted with a replay job ID, and can poll for the result.

Payload storage

PlanRetentionMax body size
Free7 days100 KB
Pro30 days100 KB
Team90 days100 KB

Bodies larger than 100 KB are stored truncated. Replaying a truncated payload is allowed but flagged with a warning in the dashboard and in the x-hookman-truncated: true header on the forwarded request.

After the retention window, the payload expires automatically. The log entry remains, but the replay button is disabled.

Disabling payload storage

If you have compliance reasons to not store webhook bodies, you can disable storage per-project:

Dashboard: Project → Settings → Payload storage → Disable

When disabled, Hookman still logs the event metadata but stores nothing. The replay button is permanently disabled for all logs from that project.

Triggering a replay

From the dashboard

  1. Open your project and go to the Logs tab
  2. Click any log entry to open the detail panel
  3. Click Replay — a dropdown lets you choose the target deployment
  4. The replay is queued immediately; the panel shows live status

From the API

Terminal window
curl -X POST https://api.hookman.dev/api/orgs/acme/projects/payments/logs/log_xxx/replay \
-H "Authorization: Bearer hm_live_xxx" \
-H "Content-Type: application/json" \
-d '{ "deployment_id": "dep_xxx" }'

Omit deployment_id to replay to the currently active deployment.

Response:

{
"replayId": "rpl_xxx",
"status": "queued"
}

Poll for result

Terminal window
curl https://api.hookman.dev/api/orgs/acme/replays/rpl_xxx \
-H "Authorization: Bearer hm_live_xxx"
{
"id": "rpl_xxx",
"status": "delivered",
"newLogId": "log_yyy",
"completedAt": 1741789381000
}

What gets forwarded

The replay sends the original request almost unchanged:

  • Method — same as the original (typically POST)
  • Body — stored payload body, verbatim
  • Headers — original request headers, with sensitive headers stripped (Authorization, Cookie)

Added headers:

x-hookman-replay: true
x-hookman-original-log-id: log_xxx
x-hookman-truncated: true ← only if body was truncated

Your application can detect replays via x-hookman-replay: true if you need different behaviour (e.g. skip idempotency checks, skip sending confirmation emails).

Replay log entries

Each replay creates a new log entry — the original is never modified. The new entry has:

  • is_replay: true
  • original_log_id linking back to the source
  • Its own response status, latency, and error

In the Logs tab, replays appear with a ↩ Replay badge and a link to the original event.

Idempotency

Hookman does not enforce idempotency on replays — it just re-delivers. Your application is responsible for handling duplicate deliveries if that matters (most webhook providers use an event ID in the payload for exactly this reason).

For Stripe webhooks, the id field in the payload is the event ID. You can use this to deduplicate in your database.