Retries & replay
How Hoursmith delivers webhooks — timeouts, the exponential backoff retry schedule, idempotency, replaying past deliveries, and test events.
This page covers how deliveries succeed or fail, how Hoursmith retries them, and how to make your handler safe against duplicates.
Delivery
Events are delivered as an HTTP POST with Content-Type: application/json and roughly a
10-second timeout:
- A 2xx response means success.
- Anything else — a non-2xx status or a timeout — means failure and triggers a retry.
Return your 2xx fast and do the real work asynchronously. If your handler does heavy processing inline and exceeds the ~10-second budget, the delivery is counted as failed even though you received it.
Retry schedule
Failed deliveries are retried with exponential backoff at these offsets, in seconds, with a little jitter:
| Attempt | Offset after the original |
|---|---|
| Retry 1 | 5 seconds |
| Retry 2 | 60 seconds |
| Retry 3 | 300 seconds (5 minutes) |
| Retry 4 | 1,800 seconds (30 minutes) |
| Retry 5 | 7,200 seconds (2 hours) |
| Retry 6 | 21,600 seconds (6 hours) |
| Retry 7 | 86,400 seconds (24 hours) |
That's 7 attempts spread over roughly 32 hours. After the 7th failed attempt, the delivery is marked failed and the endpoint's status flips to "failing".
A "failing" endpoint stays enabled — Hoursmith keeps sending new events to it. It's up to you to fix the receiver or disable the endpoint. See Troubleshooting and My webhook is failing.
Idempotency
All retries of one event share the same event id. Because of this — and because replays reuse
the same id too — your handler should be idempotent: processing the same event twice must have
the same effect as processing it once.
The simplest approach is to dedupe on the event id:
- When an event arrives, check whether you've already recorded its
id. - If you have, acknowledge with a 2xx and stop.
- If you haven't, record the
id, process the event, then acknowledge.
async function handleEvent(event) {
// Dedupe: store ids and skip ones you've already handled.
const isNew = await markProcessed(event.id); // false if already seen
if (!isNew) return;
switch (event.type) {
case 'invoice.paid':
await onInvoicePaid(event.data.invoice, event.data.payment);
break;
// ...other event types
}
}Replaying a delivery
An Owner or Admin can replay a past delivery from
Settings → Webhooks (hoursmith.app/settings/webhooks).
A replay reuses the same event id with a fresh timestamp and signature.
Because the id is unchanged, an idempotent handler treats a replay safely — useful
for re-delivering events your endpoint missed while it was down.
Test events
When you add an endpoint, an Owner or Admin can send a synthetic test event to confirm it's wired up. Test events:
- carry
livemode: false, and - include
data.test: true.
Use the livemode flag (and data.test) to keep test deliveries out of your production data paths.
Housekeeping
Delivery history is pruned automatically:
- Succeeded deliveries are kept for about 30 days.
- Failed deliveries are kept for about 90 days.
Verify signatures
Validate the Hoursmith-Signature header with an HMAC-SHA256 check over the raw request body, with Node.js and Python examples.
Rotate the signing secret
Rotate a webhook endpoint's signing secret in place. There is no dual-signing window, so update your receiver in lockstep with the rotation.