Delivery and retries
Events are queued when they happen and sent by a background worker, so delivery is not synchronous with the action that caused it. Emission is fire-and-forget on CredenShare's side: if your endpoint is unreachable, the share, request or membership change that triggered the event still succeeds.
The request
CredenShare sends a POST to the URL you registered, with a JSON body.
| Header | Value |
|---|---|
Content-Type | application/json |
User-Agent | CredenShare-Webhooks/1 |
X-CredenShare-Event | The event code, for example share.created |
X-CredenShare-Delivery | The delivery's id, a UUID |
X-CredenShare-Signature | t=<unix-seconds>,v1=<hex> — see Verifying signatures |
Standard HTTP headers (Host, Content-Length, Accept-Encoding) are set by the HTTP client as usual. You cannot add headers of your own, change the method, or change the content type; an endpoint's URL is also fixed once created.
A delivery on the wire takes this shape:
POST /your-webhook-path HTTP/1.1
Host: hooks.example.com
Content-Type: application/json
User-Agent: CredenShare-Webhooks/1
X-CredenShare-Event: share.deleted
X-CredenShare-Delivery: 3f1b9c74-5e2a-4b6d-9c88-1a0e7d452fb1
X-CredenShare-Signature: t=<unix-seconds>,v1=<hex>
<flat, metadata-only JSON carrying an "event" key>
Do not assume a particular key order or spacing in the body. Read it as JSON, and if you are verifying the signature, hash the bytes you received rather than anything you re-serialize.
A few consequences worth designing around:
- Redirects are not followed. A
3xxis treated as a failed attempt, not as a pointer to somewhere else. Register the final URL. - HTTPS only, and the hostname must resolve to a public address. This is re-checked before every delivery, not only at registration.
- Each attempt times out after 10 seconds — both connecting and the request as a whole.
What counts as success
Any 2xx status. The response body is ignored.
Anything else is a failed attempt, and what happens next depends on the status:
| Response | What CredenShare does |
|---|---|
2xx | Marks the delivery delivered. Done. |
408 or 429 | Retries on the schedule below. |
Any other 4xx | Marks the delivery dead immediately. No further attempts, however many were left. |
5xx | Retries on the schedule below. |
3xx | Not followed. Counts as a failed attempt and is retried. |
| Timeout, connection refused, TLS failure | Retried. |
The reasoning behind the 4xx rule: a 4xx says the request itself is unacceptable, and CredenShare will send a byte-identical request on the next attempt, so repeating it cannot help. 408 and 429 are the two 4xx codes that mean "the request was fine, try it again later", so they stay on the schedule.
400 an expensive default for rejecting a delivery. If your receiver returns 400 for a signature mismatch, a temporary misconfiguration on your side turns every event that arrives during it into a permanent loss. Return 500 (or 429) for anything you expect to be able to fix, and reserve 4xx for a request you genuinely never want again.The retry schedule
A delivery gets at most 6 HTTP attempts. Each wait is five times the last, capped at six hours:
| After attempt | Wait | Elapsed since the first attempt |
|---|---|---|
| 1 | 1 minute | 1m |
| 2 | 5 minutes | 6m |
| 3 | 25 minutes | 31m |
| 4 | 2 hours 5 minutes | 2h 36m |
| 5 | 6 hours | 8h 36m |
So the sixth and final attempt lands roughly 8 hours 36 minutes after the first. After it fails, the delivery is marked dead and left alone.
Two things affect when the first attempt arrives. The worker sweeps the queue once a minute, so expect up to about a minute between the event and the first attempt. Each sweep takes a bounded batch (currently up to 50 deliveries) and works through it one at a time, so a burst of events can take a few minutes to clear.
Duplicates
Deduplicate on X-CredenShare-Delivery. Retries of the same delivery reuse the same id, so a receiver that acknowledged after its own timeout can recognise the repeat. A replay is a new delivery and gets a new id, which is deliberate: a replay is a request for the event to be handled again.
When an endpoint is disabled
Repeated failures do not disable your endpoint. There is no failure threshold, no automatic pause and no cool-off. Deliveries die individually, and new events keep being queued and attempted.
CredenShare disables an endpoint by itself in exactly two situations:
| Trigger | What happens |
|---|---|
| The endpoint's hostname resolves to a private, loopback or otherwise reserved address at delivery time | That delivery is marked dead and the endpoint is disabled with a reason recorded against it |
| The endpoint's signing secret can no longer produce verifiable signatures, because the underlying signing key changed | Deliveries are marked dead and the endpoint is disabled with a reason telling you to re-create it |
Both cases record a reason against the endpoint, reported alongside its disabled state. For the first, make the hostname resolve to a public address again and re-enable the endpoint from the app; because an endpoint's URL cannot be changed after creation, an endpoint pointing at a name you cannot fix has to be replaced. For the second, rotating the secret will not help — create a new endpoint and configure your receiver with its new secret.
You can also disable an endpoint yourself, from the Webhooks card in your account's Security settings. A disabled endpoint stops receiving immediately: events that occur while it is disabled are not queued for it and cannot be replayed later, because no delivery was ever created.
Dead deliveries, the delivery log, and replay
There is no dead-letter queue and no separate failure notification. A delivery that exhausts its attempts, or that a 4xx killed outright, simply stays in the endpoint's delivery log with the status dead. Recovery is a deliberate act: you look at the log and replay what you want.
A delivery moves through these states:
| Status | Meaning |
|---|---|
pending | Queued, first attempt not yet made |
delivering | An attempt is in flight |
delivered | A 2xx was received |
failed | An attempt failed and another is scheduled |
dead | No further attempts will be made |
Reading the log
Open your account page, go to the Security tab, find the Webhooks card, and choose Deliveries on the endpoint you care about. The table shows the event code, the status, the number of attempts, the HTTP status code your endpoint last returned, when the delivery was created, and a Replay action. It lists the 50 most recent deliveries, newest first.
The delivery record also retains the error text of the last failure and the payload that was sent, which is what makes a failure diagnosable after the fact: you can see whether your endpoint answered 502, timed out, or rejected the request outright.
Replaying
Replay creates a new delivery carrying the original payload, verbatim. Your endpoint receives what it missed, not the current state of the object — which is the point, but it means the payload can describe something that has since changed or been deleted. Treat a replayed event as a record of what happened, and fetch current state if you need it.
The original delivery's attempt history is left intact, so the log keeps showing what actually failed. The replay is a fresh delivery: it gets its own X-CredenShare-Delivery id, its own signature and timestamp, and its own six attempts.