Skip to content
The Escalation Desk

case 1 of 3 · webhook integrity

The silent webhook: 41 hours of stale statuses nobody reported

In plain terms: Every time a delivery moves, we send an automatic message — a webhook — to the customer’s own software so their system knows without asking. Axlebrook Parts shipped a change to that system, and the address those messages land on started rejecting every one of them. The deliveries never stopped; only the reporting did. So nothing looked broken to them, while their staff quoted day-old information to callers.

sev2Axlebrook Partsopened May 8, 9:12 AM ETspotted by: Board webhook-health column: consecutive-failure trip wire (A9)

01Symptom

Nobody called. That is the symptom. Axlebrook Parts pushed a change to their order-management system (OMS) on Wednesday evening; since May 6, 9:04 PM ET their webhook endpoint — the address on their side where our status messages land — has answered every delivery-event POST with an HTTP 500, the answer a server gives when it crashes. Their deliveries are unaffected — 371 have completed normally since the deploy — but the status updates describing those deliveries stopped reaching their OMS 41 hours ago.

Their ops team has been quoting stale delivery states to callers for a day and a half without knowing it. The board’s webhook-health column has read failing since Wednesday night; this morning’s board review turned it into a case. The customer still hasn’t noticed. The case below is the live investigation.

Last 2xx from endpoint

May 6, 9:01 PM

2xx is the success answer a server gives. Theirs has given none since.

Consecutive failures

5,928

Events dropped (retries exhausted)

2,412

Re-sent until the tries ran out, then thrown away.

Orders affected

379

02Evidence trail

The webhook request log, at the boundary. Three healthy deliveries, then the deploy lands and every attempt — including all retries — dies with a 500 in under 100 ms:

Each row is one try at handing one status message to their system. The attempt column reads try number over tries allowed, so the same event comes back around after each failure. Green http means their system took the message; red means it did not.

Webhook delivery attempts around the failure boundary
ts (ET)eventtypeattempthttplatency
May 06 20:59:16evt_ixjd72delivery.updated1/6200141 ms
May 06 21:00:03evt_ixjd7gdelivery.updated1/6200272 ms
May 06 21:00:50evt_ixjd7udelivery.updated1/6200138 ms
May 06 21:04:25evt_ixjd88delivery.courier_location_updated1/650075 ms
May 06 21:05:04evt_ixjd8mdelivery.updated1/650097 ms
May 06 21:05:26evt_ixjd88delivery.courier_location_updated2/650051 ms
May 06 21:06:08evt_ixjd8mdelivery.updated2/650096 ms
May 06 21:06:35evt_ixjd90delivery.courier_location_updated1/650061 ms
May 06 21:07:31evt_ixjd9edelivery.updated1/650051 ms
May 06 21:07:44evt_ixjd90delivery.courier_location_updated2/650042 ms
May 06 21:08:32evt_ixjd9sdelivery.courier_location_updated1/650070 ms
May 06 21:08:33evt_ixjd9edelivery.updated2/650076 ms
May 06 21:09:19evt_ixjda6delivery.updated1/650076 ms
May 06 21:09:29evt_ixjd88delivery.courier_location_updated3/650044 ms
May 06 21:09:37evt_ixjd9sdelivery.courier_location_updated2/650064 ms
May 06 21:10:08evt_ixjd8mdelivery.updated3/650062 ms
May 06 21:10:20evt_ixjda6delivery.updated2/650055 ms
May 06 21:11:43evt_ixjd90delivery.courier_location_updated3/650070 ms
May 06 21:12:35evt_ixjd9edelivery.updated3/650065 ms
May 06 21:13:35evt_ixjd9sdelivery.courier_location_updated3/650051 ms
May 06 21:14:19evt_ixjda6delivery.updated3/650091 ms

Showing the boundary window; the pattern repeats for every event since. Event shapes per the documented envelope and event types (burq.readme.io/reference/webhooks).

One event’s retry ladder on the clock: when a message fails we send it again, each wait longer than the last. Burq’s blog checklist confirms retries exist; the exact schedule below is illustrative (ASSUMPTIONS.md A1: 0s · +1m · +5m · +30m · +2h · +6h). After the final attempt the event is dropped — thrown away for good. It will not come back on its own once the endpoint recovers, which is why a dead endpoint doesn’t heal itself, and why this needed a phone call rather than patience:

  1. attempt 1first attemptMay 06 21:04:25HTTP 500
  2. attempt 2+1mMay 06 21:05:25HTTP 500
  3. attempt 3+5mMay 06 21:09:25HTTP 500
  4. attempt 4+30mMay 06 21:34:25HTTP 500
  5. attempt 5+2hMay 06 23:04:25HTTP 500
  6. attempt 6+6hMay 07 03:04:25HTTP 500→ event dropped

The gap stale data opens. Left: what Burq knows. Right: the last status Axlebrook Parts’s OMS ever received for the same delivery:

Burq-side status vs customer-side last received status
orderburq statusat (ET)customer last receivedat (ET)
AXL-20260506-1115deliveredMay 06 23:10:00delivery_createdMay 06 21:55:00
AXL-20260507-5601deliveredMay 07 16:09:00delivery_createdMay 07 14:41:00
AXL-20260507-2483deliveredMay 07 16:15:00delivery_createdMay 07 14:43:00
AXL-20260507-1748deliveredMay 07 16:34:00delivery_createdMay 07 15:40:00
AXL-20260507-2403deliveredMay 07 17:57:00delivery_createdMay 07 17:00:00
AXL-20260507-8980deliveredMay 07 17:40:00delivery_createdMay 07 16:53:00
AXL-20260508-7398deliveredMay 08 09:36:00delivery_createdMay 08 08:36:00
AXL-20260508-5877deliveredMay 08 09:43:00delivery_createdMay 08 08:43:00

Each row is one order. The right-hand column is what their staff reads out when a customer phones in.

the query that surfaced itops warehouse · illustrative schema (A8)
-- Which endpoint stopped accepting, and when?
SELECT
  account_id,
  MAX(ts) FILTER (WHERE http_status BETWEEN 200 AND 299) AS last_2xx_at,
  COUNT(*) FILTER (WHERE http_status >= 400
                     AND ts > now() - INTERVAL '24 hours') AS failed_24h,
  COUNT(*) FILTER (WHERE ts > now() - INTERVAL '24 hours') AS total_24h
FROM webhook_attempts
GROUP BY account_id
ORDER BY last_2xx_at ASC;

03Diagnosis

The whole diagnosis is two log reads, because the question has exactly two suspects: our events stopped firing or your endpoint stopped accepting.

Read one: Burq-side status events kept appending all day — deliveries created, picked up, delivered, on schedule. So the pipeline that generates events is healthy. Read two: the webhook attempt log shows us POSTing on schedule the entire time, every attempt answered with a 500 in under 100 ms. The speed is the tell. A dead host or a broken network makes the sender wait and then give up with nothing; an answer that fast means something on their side picked the message up, tried to handle it, and fell over. Their application is crashing on our request — not a network problem, not silence on our side. If events had stopped firing, that table would be empty, not red.

The timestamp seals it: last 2xx at May 6, 9:01 PM ET, first 500 at 9:04 PM — squarely inside their Wednesday-evening deploy window.

the two readsops warehouse · illustrative schema (A8)
-- Read 1: is Burq-side delivery state still moving? (yes → pipeline healthy)
SELECT date_trunc('hour', occurred_at) AS hr,
       COUNT(*)                        AS status_events
FROM delivery_status_events
WHERE account_id = 'acct_axlebrook'
  AND occurred_at > now() - INTERVAL '48 hours'
GROUP BY 1
ORDER BY 1;

-- Read 2: are we still trying to deliver events? (yes — and every attempt
-- since the deploy dies with a 500 in under 100 ms)
SELECT http_status,
       COUNT(*)  AS attempts,
       MIN(ts)   AS first_seen,
       MAX(ts)   AS last_seen,
       ROUND(AVG(latency_ms)) AS avg_latency_ms
FROM webhook_attempts
WHERE account_id = 'acct_axlebrook'
  AND ts > now() - INTERVAL '48 hours'
GROUP BY http_status
ORDER BY http_status;

what the operator did not do

  • Page Burq engineering — read one shows the delivery pipeline green. Paging would burn engineering time on a customer-side fault.
  • Blind-replay dropped events — the endpoint is still down; replays would die the same way. Fix first, re-sync second.
  • Wait for the customer to notice — 41 hours of silence is the argument for calling them, not for waiting longer.

04Resolution & prevention

Resolution is on their side: roll back or patch the Wednesday deploy. Once the endpoint returns 2xx, we send the list of 379 affected orders and they re-sync — ask us for each order’s current state and catch their own records up — via GET /v2/orders/{id} with expand=latest_delivery — the documented read path. Same-day recovery.

Prevention is on ours. This gap was caught by a threshold, not a human — but 41 hours is still too slow. The alert below pages after 6 consecutive terminal failures (messages that used up every retry), which for an account at this volume means minutes, not days. This failure mode is the reason the board carries a webhook endpoint health column at all.

the monitoring rule that now pages usops warehouse · illustrative schema (A8)
-- Prevention: page when the last 6 attempts to any endpoint all failed (A9).
WITH last_n AS (
  SELECT account_id, http_status,
         ROW_NUMBER() OVER (PARTITION BY account_id ORDER BY ts DESC) AS rn
  FROM webhook_attempts
)
SELECT account_id
FROM last_n
WHERE rn <= 6
GROUP BY account_id
HAVING COUNT(*) = 6
   AND COUNT(*) FILTER (WHERE http_status BETWEEN 200 AND 299) = 0;

the outreach — same facts, two altitudes

To: Sara Jennings (Integrations, Axlebrook)

Subject: Your Burq webhook endpoint has been returning 500s since May 6, 9:04 PM ET

Hi Sara, Your webhook endpoint (oms.axlebrook.example/hooks/burq) has failed every delivery since May 6, 9:04 PM ET — HTTP 500 on all attempts, 5,928 consecutive failures as of this morning. The last 2xx we logged was May 6, 9:01 PM ET, which lines up with an evening deploy window on your side. Two log lines that tell the story: our delivery pipeline is green (deliveries kept completing all day yesterday — 371 since the outage began), and every POST to your endpoint since 9:04 PM Wednesday has died with a 500 in under 100ms. Events stopped being accepted, not sent. We retry each event 6 times over ~6h (0s/+1m/+5m/+30m/+2h/+6h), so everything older than that window — 2,412 events — has been dropped and will not replay on its own. Once the endpoint is fixed, re-sync open orders via GET /v2/orders/{id} (we can send the affected order list, 379 orders). Can you check the May 6 deploy? Happy to get on a call and replay a signed test event.