Deliveries, retries, and troubleshooting.
How retries work, when an endpoint pauses itself, and how to fix a rejected or missing delivery.
A delivery is one attempt to send one event to one endpoint. Most deliveries succeed on the first try. When your address is slow, unreachable, or briefly down, Reward Loyalty retries a few times on its own before giving up, and every attempt is recorded so you can see exactly what happened. This page covers testing an endpoint, the delivery log, the retry schedule, what happens when an endpoint fails again and again, duplicate deliveries, and a troubleshooting table for common problems.
Testing an endpoint
Two buttons on each endpoint send something without waiting for a real sale, each limited to 10 sends per minute:
- Send ping delivers a
pingevent with an emptydataobject ({}). It proves the URL is reachable and your signature check works.pingnever appears as a subscription choice. - Send sample delivers one full example of an event the endpoint subscribes to. The values are made up on the spot: random ids and the member identifier
SAMPLE-0001, in the same shape as a real delivery. Nothing is created in your account and no real member is used.
Both arrive signed, through the same pipeline as production events, with test: true in the envelope. The deliveries log labels them Test, and they are excluded from delivery analytics and from the auto-pause counter described below.
The deliveries log
Recent deliveries, under each endpoint, shows its most recent 25 deliveries: the event, status, response code, attempt count, and time. A test send carries a Test label so it stands out from real activity. A delivery moves through these statuses:
| Status | Meaning |
|---|---|
| Pending | Queued, not sent yet |
| Retrying | A first attempt failed in a way that retries; waiting for the next one |
| Delivered | A 2xx response came back; done |
| Failed | Every attempt is used up, or the endpoint gave a final rejection; nothing more will be sent for this delivery |
There is no manual replay of a past delivery. When a delivery ends in Failed, fix the endpoint and use Send sample to confirm it, or wait for the next real event.
Delivery, retries, and timeouts
A webhook never blocks or slows the sale that triggered it. Every matching event first becomes a pending delivery record, and that record is sent with a connect timeout of 3 seconds and a total timeout of 5 seconds. Redirects are not followed. A 2xx response counts as delivered; everything else is a failed attempt:
- Retried: HTTP 408, 425, 429, any 5xx, and attempts that produced no response at all (DNS failure, connection failure, timeout, or a URL the platform refused to contact because it no longer resolves to a public address).
- Final: any other response, including other 4xx codes and redirects. That delivery will not be retried.
A delivery gets three attempts in total. How fast they happen depends on how the installation runs:
- With queue workers, the first attempt starts within seconds on a dedicated
webhooksqueue, and the worker retries 10 and 30 seconds after a retryable failure. - Without queue workers (the default
syncsetup), the first attempt runs right after the page that created the event finishes responding. Retries come from the database: 60 seconds after the first failed attempt and 5 minutes after the second. The third failed attempt is final.
A background delivery runner sends whatever is due: every minute through the scheduler, or, on installations without a working scheduler, piggybacked on normal site traffic at most once per 60 seconds. Administrators can also run it by hand from the Health Center, which shows the pending backlog, its age, and recent failures. Bulk work such as a nightly expiry sweep only queues delivery records and leaves the sending to this runner, so a large expiry never turns into a burst of blocking requests.
When an endpoint keeps failing
Each endpoint tracks its own health:
| Status | Meaning |
|---|---|
| Active | Delivering normally |
| Failing | Recent deliveries failed, fewer than 20 in a row; still trying |
| Auto-paused | 20 failures in a row paused it automatically |
| Switched off | Turned off by the partner |
Only a final failure counts against the endpoint, once per delivery, and the reason is kept: a status code or a short error, never the response body. A delivered production event resets the count to zero. Twenty consecutive final failures pause the endpoint and email the partner who owns it; fix whatever the endpoint was rejecting, then switch it back on from the same endpoint panel used to pause it. Test sends never move this counter in either direction.
Duplicate deliveries
Delivery is at least once, never exactly once. Occasionally you will receive the same event twice, most often because a retry followed a response that never arrived: the platform sent the request, your server processed it and returned 200, but the response itself got lost on the way back. From the platform's side that attempt looks like it failed, so it retries, and your endpoint ends up doing the same work twice. Build your receiving code to expect this rather than treating it as a bug.
Handle it with the three ids:
- Deduplicate on
X-RewardLoyalty-Delivery(the body'sid). It is unique per delivery to your endpoint and identical on every retry of it. Store the ids you have processed and skip a repeat. - Correlate across endpoints on
event_id. Two of your endpoints subscribed to the same event receive two deliveries with two delivery ids but oneevent_id. - Correlate across events on
correlation_id. Every event from one operation (one sale, one void, one transfer) shares it. It isnullwhen no useful correlation exists.
Domain records give you a second, event-specific anchor when you need one: transaction_id, redemption_id, and removal_id name the underlying record and never change.
Retention and analytics
Delivery records older than 30 days are removed by a daily cleanup task, and again whenever the log is opened.
Delivery analytics, linked from the webhooks page as View analytics, shows how your deliveries performed: volume per event and group, success rate, median and 95th percentile duration, and failures by response class (2xx, 4xx, 5xx, network). Filter by endpoint, event, group, status, production or test, and date range; charts use your business time zone. Production numbers always exclude test sends.
Export CSV downloads the filtered delivery metadata: delivery id, event key, event id, status, response status, duration, timestamps, attempt count, and the test flag. Payloads, secrets, and response bodies are never exported.
After upgrading
If you upgraded from a version whose catalog had eight events, run this once on the server so deliveries recorded before the upgrade carry the new envelope metadata in the log and analytics:
php artisan webhooks:backfill-delivery-metadata
It is safe to run again; a second run changes nothing.
Troubleshooting
| Problem | Likely cause | Fix |
|---|---|---|
| I don't see Webhooks in my Integrations menu at all | Webhooks is not included in your plan, and it has not been granted to your account on top of the plan | Ask your operator to turn on Webhooks for your plan, or to grant it to your account from Administration → Partners → Permissions. See Turning it on. |
| My address was rejected when I tried to save it | The address must be a public https:// address reachable from the internet. A localhost, private-network, or plain http:// address cannot work |
Use a public HTTPS address, for example the URL Zapier or Make gave you, or your server's real public domain |
| I see no deliveries at all | One of four things: the endpoint is not Active, the event is not subscribed on that endpoint, your business does not currently have the card type that event needs, or you triggered a production action while the log was filtered to Test | Check the endpoint's Active switch, its selected events, that the relevant feature (loyalty, stamps, vouchers, or passes) is enabled for your business, and the Production/Test filter on the deliveries log |
| Signature never matches | The body was parsed and re-encoded before verification, or the secret is stale after a rotation | Verify against the raw request bytes, and re-copy the secret from the endpoint panel |
| Deliveries fail with 4xx | Your endpoint rejects the request: wrong path, auth in front of it, or validation. These are final, so nothing retries | Fix the receiver, confirm with Send sample, then watch the next real event |
| Deliveries fail with 5xx | Your endpoint errors while handling the request. These retry up to the three-attempt limit | Fix the receiver before the failures accumulate toward auto-pause |
| Deliveries time out | The endpoint takes longer than the 3-second connect or 5-second total limit | Respond 200 first and process the event afterward |
| "Blocked" failures with no response code | The URL stopped resolving to a public address, or DNS fails. No request leaves the platform; the attempt retries | Point the endpoint at a public HTTPS host and check its DNS records |
| The same event arrives twice | At-least-once delivery: a retry followed a response that never arrived | Deduplicate on X-RewardLoyalty-Delivery |
| An expected event never arrives | The event is not selected on that endpoint, the endpoint is paused, or the action maps to a different key, for example waiting for purchase.recorded on a points-only credit |
Check the endpoint's selected events against the catalog, and its status |
Related topics
- Webhooks overview and setup: Add an endpoint, pause or re-enable it, and rotate its secret
- Events and member synchronization: The full event catalog and field tables
- Security and signature verification: Verify a delivery came from Reward Loyalty