The delivery log
Every webhook and pixel delivery, with the request that was sent and the response that came back — and a replay for the ones that failed.
After this page you can answer "did that call reach our system?" without guessing, tell a partner exactly what their server said, and recover a whole outage with one button. A webhook that fails quietly is worse than no webhook, because you believe it is working — so every attempt the platform makes is a row here, the successes included, and nothing is ever dropped without leaving one.
The log is on each endpoint under Webhooks & pixels in the console, and on GET /api/tracking/webhooks/:id/deliveries. It covers webhooks and pixels alike: they are the same delivery machinery with two shapes on the wire.
One row per event, not per attempt
A delivery that failed three times and then landed is one story with a happy ending. A flat table of attempts prints it as three failures and a success on four unrelated rows, which is how an endpoint that is working ends up looking broken. So the log groups by requestId — the event's own id, stable across every retry and every replay — and puts the attempts inside it.
{
"requestId": "req_5f0c2a91b7d34e10",
"event": "call.completed",
"callId": "0b9f6c1e-52a4-4a0e-9f0a-3d1c7e8b2a11",
"state": "delivered",
"attempts": 3,
"firstAt": "2026-09-20T14:06:24.190Z",
"lastAt": "2026-09-20T14:06:55.402Z",
"lastCode": 200,
"payload": { "callId": "0b9f6c1e-52a4-4a0e-9f0a-3d1c7e8b2a11", "ref": "CA-482137", "…": "the event's payload" },
"deliveries": [
{
"id": "914022",
"requestId": "req_5f0c2a91b7d34e10",
"event": "call.completed",
"method": "POST",
"attempt": 1,
"state": "failed",
"ok": false,
"code": 502,
"ms": 148,
"at": "2026-09-20T14:06:24.190Z",
"url": "https://hooks.example.com/buy3",
"error": "Answered HTTP 502",
"responseBody": "<html><head><title>502 Bad Gateway</title></head>",
"requestBody": null
},
{
"id": "914037",
"requestId": "req_5f0c2a91b7d34e10",
"attempt": 2,
"state": "failed",
"ok": false,
"code": 0,
"ms": 10004,
"at": "2026-09-20T14:06:29.331Z",
"error": "Timed out after 10000ms",
"responseBody": null
},
{
"id": "914061",
"requestId": "req_5f0c2a91b7d34e10",
"attempt": 3,
"state": "delivered",
"ok": true,
"code": 200,
"ms": 91,
"at": "2026-09-20T14:06:55.402Z",
"error": null,
"responseBody": "{\"received\":true}"
}
]
}| Field | Meaning |
|---|---|
requestId | The event's id. It is the envelope's id and the buy3-request-id header, and it is what your receiver de-duplicates on. |
event | Which event fired. |
callId | The call it was about, or null for an event that is not about a call. Filter on it to see everything ever sent about one call. |
state | The event's state, worked out from its attempts — see below. |
attempts | How many attempts have actually been made. A queued one is not counted, because it has not happened yet. |
firstAt, lastAt | The first and the most recent attempt. |
lastCode | The most recent HTTP status that came back, or null if none ever did. |
payload | The event's data, printed once: it is identical on every attempt, and repeating it four times would triple the size of the page. |
deliveries | Every attempt, oldest first. |
What each attempt records
| Field | Meaning |
|---|---|
attempt | 1 for the first try, counting up. A number past the ceiling means somebody replayed it. |
state | delivered, failed or queued — this attempt alone. |
code | The HTTP status your server answered with. 0 means the request never reached a server: DNS, TLS, a refused connection, a time-out, or an address the safety check would not call. null means it has not been sent yet. |
ms | Round trip, from opening the request to reading the response headers. |
url | The address that was actually requested. For a pixel this is the template after its macros were filled in, with caller identity already hashed — so the log is as safe to read as the URL was to send. |
error | Why it failed, in a sentence: Answered HTTP 502, Timed out after 10000ms, or the redirect that was offered and not followed. |
responseBody | The first 2 KB of what came back — enough to read your error page's first paragraph or your JSON refusal, not enough to be a copy of your site. |
requestBody | What was sent, for a pixel only. A webhook's body is the envelope, which is payload again; a pixel's body is the one thing its template alone cannot tell you. |
retryAfter | On a queued attempt, when its backoff expires. |
The three states
One vocabulary, used for the event, for each attempt inside it and in the state= filter — so a word you are shown is a word you can filter on.
| State | The event | Means |
|---|---|---|
delivered | Some attempt was accepted. | Done. It may have taken three tries; it landed. |
queued | None was accepted, and one is still waiting. | A retry inside its backoff, or a replay you have just pressed. Not a failure yet. |
failed | None was accepted and nothing is waiting. | The machine gave up. These are what Replay failures replays. |
curl -G "https://api.buy3.io/api/tracking/webhooks/b7c6d5e4-f3a2-4b19-8c07-6d5e4f3a2b19/deliveries" \
-H "Authorization: Bearer $BUY3_SESSION_TOKEN" \
--data-urlencode "state=failed" \
--data-urlencode "pageSize=50"event=narrows to one event name,callId=to everything ever sent about one call, andstate=to one of the three above.- Pages are 25 rows by default and at most 200, newest event first.
- A queued row is neither a success nor a failure and is printed as neither:
codeisnulland the errors-only filter leaves it out.
Endpoint health
Above the log, each endpoint carries its last 24 hours of stamped deliveries. The tiles read Attempts, last 24h, Delivered, Failed and p95 round trip.
- p95, not an average. A receiver sitting at three seconds has not failed yet and is the more useful thing to catch; an average hides it behind the many fast responses. The 95th percentile is where the trouble lives, and a p95 climbing towards ten seconds is a time-out you have not had yet.
lastStatusof0on the endpoint means the most recent attempt never reached a server at all. That is a DNS, TLS or firewall answer, not an application one.- Delivered and failed all-time counters sit beside the window, under History, so "has this ever worked" and "is it working now" are two different questions with two different answers.
- An endpoint that fails two deliveries in a row is marked
degradedand the console says so in a banner. The first success clears it. See Webhooks.
Replay
Replay is the human override of the give-up rule. The original event and its original payload are queued again under the same request id, with the attempt counter carried forward — so the log shows a fifth attempt on an event the machine stopped at four, which is exactly what a manual replay should leave behind.
# One event, again
curl -X POST "https://api.buy3.io/api/tracking/webhook-deliveries/914061/replay" \
-H "Authorization: Bearer $BUY3_SESSION_TOKEN"
# Everything still failing on this endpoint, one replay per event
curl -X POST "https://api.buy3.io/api/tracking/webhooks/b7c6d5e4-f3a2-4b19-8c07-6d5e4f3a2b19/replay-failures" \
-H "Authorization: Bearer $BUY3_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "limit": 500 }'{
"queued": 183,
"note": "Queued in order. Watch the delivery log for the results."
}| Replay one | Replay failures | |
|---|---|---|
| Scope | The event you clicked. | Every event on the endpoint whose latest attempt is still failing. |
| How many | One. | Up to limit, default 200, at most 500. |
| Skips | A replay already waiting — two clicks are one replay. | Anything that later succeeded under the same request id, and anything already queued. |
| Refuses | A delivery that is still queued (409). | Nothing, but it answers queued: 0 when there is nothing to do. |
| On a paused endpoint | 409. Resume it first — a paused endpoint is never called, so the replay would sit in the log for ever. | 409, for the same reason. |
- Replaying works per event, never per attempt. A four-attempt failure replayed four times would be an outage of your own making.
- A replay of a degraded endpoint does not declare it healthy. It proves that on the next delivery — one success flips it back to active.
- Every replay is written to the activity log, with who pressed it and how many events it queued.
- Send test is the third button: it queues a sample delivery built from your newest real call, marked
test: true, without waiting for a real one. At most five test deliveries may be waiting on an endpoint at once.
Worked example: "we never got that call"
A partner says call CA-482137 never reached their system. You have the reference, which is all you need.
- Open the call in the call log and copy its
id. - Open the endpoint and filter the delivery log by that
callId. - Read what comes back:
- No rows. Nothing was ever sent. Either the endpoint is subscribed to an event this call never fired, or it is narrowed to another campaign or publisher, or it was paused at the time. None of those is a delivery problem.
- Rows, state
delivered. It was accepted.lastCodeis their200andresponseBodyis what they answered with — that ends the conversation, politely. - Rows, state
failed. Open the attempts.Answered HTTP 502is their server;Timed out after 10000msis their server being slow;code: 0with a DNS error is their hostname. - Rows, state
queued. It is mid-retry or mid-replay.retryAftersays when.
- For a pixel, compare
url— the expanded one — with the partner's documentation, parameter by parameter. Most "we never got it" turns out to be "we got it under a parameter name we do not read". - If it failed and their side is fixed, press Replay on that one event. If a whole window failed, press Replay failures instead.
curl -G "https://api.buy3.io/api/tracking/webhooks/b7c6d5e4-f3a2-4b19-8c07-6d5e4f3a2b19/deliveries" \
-H "Authorization: Bearer $BUY3_SESSION_TOKEN" \
--data-urlencode "callId=0b9f6c1e-52a4-4a0e-9f0a-3d1c7e8b2a11"One caution: a receiver that answers 200 to everything, including nonsense, looks perfect here. The log proves the request was accepted, not that it was understood. When the log says delivered and the partner says nothing arrived, the next thing to check is their parsing, with the exact body from payload in front of both of you.
What the log does not keep
- Headers. Neither the ones sent nor the ones returned. A custom header is where a receiver's bearer token lives, and a log is not the place for it.
- More than 2 KB of a response, or more than 8 KB of a pixel's request body.
- A webhook's request body as a separate field — it is the envelope, and
payloadalready holdsdata. - Anything after the endpoint is deleted. Deliveries are removed with their endpoint. Pause an endpoint you may want the history of; do not delete it.
