GUIDE
Webhooks
Register an endpoint with POST /v1/webhooks. The response contains a
secret, shown once.
Verifying what we send
X-RR-Event: delivery.delivered
X-RR-Event-Id: evt_01JAV3K2QX8M4N7P
X-RR-Timestamp: 1790000000
X-RR-Signature: 3d9e7b...
expected = hmac_sha256(secret, timestamp + "." + sha256_hex(raw_body))
Compare in constant time and reject anything older than five minutes.
X-RR-Signature may contain more than one signature, comma separated.
Accept the message if any of them matches:
X-RR-Signature: 3d9e7b...,a1c4f8...
This is how a secret is rotated without breaking you. When you rotate, we sign with both the new secret and the previous one for seven days, so your endpoint keeps working whether or not you have deployed the new value yet: deploy when it suits you, not when we say so.
Day to day there is exactly one signature and no comma. Split on the comma anyway: a verifier that compares the whole header exactly will pass every test you run and then fail the first time a secret is rotated.
$expected = hash_hmac('sha256', $timestamp.'.'.hash('sha256', $rawBody), $secret);
$ok = false;
foreach (explode(',', $header) as $candidate) {
$ok = hash_equals($expected, trim($candidate)) || $ok; // no early return
}
To rotate: POST /v1/webhooks/{id}/rotate. The response carries the new secret
once, and the date the previous one stops being accepted.
Events
delivery.created · delivery.dispatched · delivery.dispatch_failed ·
delivery.assigned · delivery.picked_up · delivery.in_transit ·
delivery.delivered · delivery.failed · delivery.cancelled
What to expect
- At least once. Deduplicate on
X-RR-Event-Id. - Not ordered. Each payload carries the full delivery with
updated_at; ignore anything older than what you hold. - Statuses can move backwards: an unassigned courier returns
assignedtodispatched. Do not assume monotonic progress. - Answer 2xx within 10 seconds. Acknowledge first, work afterwards.
- We retry 8 times over ~24 hours. An endpoint failing for 48 hours is disabled and you are notified.
Webhooks are a convenience, never the sole source of truth. If you have heard
nothing, poll GET /v1/deliveries/{id}.