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
| Plan | Retention | Max body size |
|---|---|---|
| Free | 7 days | 100 KB |
| Pro | 30 days | 100 KB |
| Team | 90 days | 100 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
- Open your project and go to the Logs tab
- Click any log entry to open the detail panel
- Click Replay — a dropdown lets you choose the target deployment
- The replay is queued immediately; the panel shows live status
From the API
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
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: truex-hookman-original-log-id: log_xxxx-hookman-truncated: true ← only if body was truncatedYour 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: trueoriginal_log_idlinking 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.