Retry policy
When a webhook delivery counts as failed, the exact retry schedule, at-least-once delivery, ordering with data.sequence, and resending from the portal.
What counts as success
A delivery succeeds when your endpoint returns any 2xx status within 10 seconds.
Anything else is a failure and is retried:
- a non-
2xxstatus, including4xxand5xx - a redirect (
3xx). Redirects are never followed. - no response within 10 seconds
- a connection or TLS error
Retry schedule
After the first attempt fails, Rehabify makes 8 retries. Each delay is measured from the previous attempt, so the whole schedule spans about 44.6 hours. The attempt number is sent in the X-Rehabify-Delivery-Attempt header.
Attempt (X-Rehabify-Delivery-Attempt) | Delay after the previous attempt | Time since the first attempt |
|---|---|---|
| 1 (first attempt) | Immediate | 0 |
| 2 | +15 s | 15 s |
| 3 | +1 min | 1 min 15 s |
| 4 | +5 min | 6 min 15 s |
| 5 | +30 min | 36 min 15 s |
| 6 | +2 h | 2 h 36 min 15 s |
| 7 | +6 h | 8 h 36 min 15 s |
| 8 | +12 h | 20 h 36 min 15 s |
| 9 (last retry) | +24 h | 44 h 36 min 15 s (≈44.6 h) |
If attempt 9 also fails, the delivery is marked failed. Rehabify doesn't retry it again automatically.
Resending failed deliveries
You can resend any delivery from the Partner Portal under Developers → Webhooks. A resent event keeps the same event id, so your deduplication logic treats it correctly. After an outage, fix your endpoint first, then resend the failed deliveries.
To catch up on state you may have missed, you can also list recent referrals with GET /partners/referrals, using created_after and status as filters.
At-least-once delivery
Delivery is at-least-once: the same event can arrive more than once, for example when your endpoint processed it but the response was lost. Deduplicate on the event id. It's also sent as X-Rehabify-Event-Id, and it's stable across retries and resends.
Out-of-order events
Retries and network delays mean events can arrive out of order. For example, referral.in_treatment could arrive before a retried referral.booked. Use data.sequence, which increases by 1 for every event of the same referral, or data.referral.updated_at, to ignore stale events.
// Pseudo-storage: replace `db` with your database. Run inside a transaction.
export async function handleRehabifyEvent(event, db) {
if (await db.processedEvents.has(event.id)) return; // duplicate delivery
await db.processedEvents.add(event.id);
if (event.type === "ping" || event.data.test) return; // test events don't change state
const { referral, sequence } = event.data;
const current = await db.referrals.get(referral.id);
if (current && current.lastSequence >= sequence) return; // stale or out-of-order event
await db.referrals.upsert(referral.id, {
status: referral.status,
provider: referral.provider,
sessions: referral.sessions,
lastSequence: sequence,
});
}Keep your endpoint fast
- Verify the signature, store the event, and return
200straight away. Do slow work, such as calling other APIs or sending emails, in a background job. - Return
2xxfor event types you don't handle. Otherwise they'll be retried for nearly two days. - Return
4xxonly when signature verification fails. Every non-2xxresponse is retried.