Webhooks
How Pozy retries failed webhook deliveries (7 attempts over about 33 hours), when an endpoint is switched off, and how to read logs and redeliver events.
Endpoints go down: deploys, outages, an expired certificate. Pozy keeps trying, so a short outage on your side does not lose events. This page covers the retry schedule, what counts as a failure, what happens when an endpoint keeps failing, and the tools to inspect and replay deliveries.
#What counts as delivered
A delivery succeeds when your endpoint answers with any 2xx status within 5 seconds. Everything else is a failure and schedules another attempt:
| Outcome | Logged as |
|---|---|
A status outside 200 to 299, including 3xx redirects (they are not followed) |
Endpoint returned 503 |
| No response within 5 seconds | No response within 5s |
| DNS, TLS or connection error | Connection failed: ... |
The body of your response does not matter.
#The retry schedule
Each event is attempted up to 7 times in total. The delay is counted from the previous attempt and grows each time, with the longest gap capped at 24 hours:
| Attempt | Delay after the previous attempt | Roughly this long after the event |
|---|---|---|
| 1 | Immediately | 0 |
| 2 | 1 minute | 1 minute |
| 3 | 5 minutes | 6 minutes |
| 4 | 30 minutes | 36 minutes |
| 5 | 2 hours | 2 hours 36 minutes |
| 6 | 6 hours | 8 hours 36 minutes |
| 7 | 24 hours | 32 hours 36 minutes |
Retries are processed every minute, so an attempt can arrive up to a minute after its slot. Every attempt carries the same body, the same X-Pozy-Event-Id and an X-Pozy-Attempt header with its number. The signature is computed again at each attempt with the endpoint's current secret.
If the 7th attempt fails, the delivery is marked failed and Pozy stops trying. The event is not lost: it stays in the logs and you can redeliver it once your endpoint is back.
After a long outage, redeliver the failed deliveries from the logs instead of waiting for the next scheduled attempt. Because every redelivery keeps the event id, your deduplication skips anything you already processed.
#When an endpoint is switched off
Each endpoint keeps a failureCount: the number of deliveries in a row that used all 7 attempts without success. A single successful delivery resets it to 0.
When failureCount reaches 20, Pozy sets the endpoint's isActive to false and stops sending to it. This protects you from a backlog of events piling up against a URL that no longer exists. The workspace's owners and admins get a warning email at 10 failed deliveries in a row, and another email when Pozy turns the endpoint off at 20.
A turned-off endpoint gets no deliveries at all. Retries that were still pending for it close with Not sent: this webhook is turned off in the logs, and new events are not queued for it. The same applies to an endpoint you turn off yourself.
To turn it back on, fix the endpoint and update it:
curl -X PUT https://api.pozy.ai/v1/webhooks/settings \
-H "Authorization: Bearer $POZY_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "webhookId": "whk_4Tq9Lm2Vx8Rb6Nc1Pz7K", "isActive": true }'const res = await fetch("https://api.pozy.ai/v1/webhooks/settings", {
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.POZY_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ webhookId: "whk_4Tq9Lm2Vx8Rb6Nc1Pz7K", isActive: true }),
});
if (!res.ok) throw new Error((await res.json()).error);requests.put(
"https://api.pozy.ai/v1/webhooks/settings",
headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
json={"webhookId": "whk_4Tq9Lm2Vx8Rb6Nc1Pz7K", "isActive": True},
timeout=30,
).raise_for_status()Re-enabling resets failureCount to 0. Events that happened while the endpoint was off were not queued for it, so once it is back on, redeliver the ones you need from the logs. Turn the endpoint on before you redeliver: a redelivery to a turned-off endpoint is not sent.
Watch failureCount and lastFiredAt from GET /v1/webhooks/settings in your monitoring to catch a failing endpoint before it is switched off. Test deliveries never count toward it.
#Read the delivery logs
GET /v1/webhooks/logs lists deliveries, newest first. There is one log entry per delivery; attemptNumber shows how many attempts it has had so far.
curl "https://api.pozy.ai/v1/webhooks/logs?status=failed&webhookId=whk_4Tq9Lm2Vx8Rb6Nc1Pz7K&limit=20" \
-H "Authorization: Bearer $POZY_API_KEY"const res = await fetch("https://api.pozy.ai/v1/webhooks/logs?status=failed&webhookId=whk_4Tq9Lm2Vx8Rb6Nc1Pz7K&limit=20", {
headers: { Authorization: `Bearer ${process.env.POZY_API_KEY}` },
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);
for (const log of data.logs) console.log(log.event, log.statusCode, log.errorMessage);resp = requests.get(
"https://api.pozy.ai/v1/webhooks/logs",
headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
params={"status": "failed", "webhookId": "whk_4Tq9Lm2Vx8Rb6Nc1Pz7K", "limit": 20},
timeout=30,
)
for log in resp.json()["logs"]:
print(log["event"], log["statusCode"], log["errorMessage"]){
"logs": [
{
"_id": "dlv_Qm3Zt8Kp1Wx6Bn4Rc9Ly",
"webhookId": "whk_4Tq9Lm2Vx8Rb6Nc1Pz7K",
"webhookName": "Production",
"eventId": "8e0b6f8a-3c51-4b7e-9d0a-2f4c6e1b9a77",
"event": "post.published",
"test": false,
"url": "https://app.example.com/webhooks/pozy",
"status": "failed",
"statusCode": 502,
"responseBody": "<html><body>Bad Gateway</body></html>",
"errorMessage": "Endpoint returned 502",
"attemptNumber": 7,
"responseTime": 211,
"nextAttemptAt": null,
"createdAt": "2026-11-03T08:30:04.000Z"
}
],
"pagination": { "total": 1, "limit": 20, "skip": 0, "pages": 1, "hasMore": false }
}| Query parameter | What it filters |
|---|---|
status |
success or failed. |
webhookId |
One endpoint. |
event |
One event name, such as message.received. |
eventId |
Every delivery of one event, across endpoints. |
limit, skip |
Page size (up to 100, default 50) and offset. |
A delivery that is still being retried shows status: "pending" and the time of the next try in nextAttemptAt. responseBody keeps the first 2,000 characters of your endpoint's answer, responseTime is in milliseconds and requestPayload holds the exact body that was sent.
#Redeliver an event
POST /v1/webhooks/logs/redeliver sends an event again, right away, with its original body and event id:
curl -X POST https://api.pozy.ai/v1/webhooks/logs/redeliver \
-H "Authorization: Bearer $POZY_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "webhookId": "whk_4Tq9Lm2Vx8Rb6Nc1Pz7K", "eventId": "8e0b6f8a-3c51-4b7e-9d0a-2f4c6e1b9a77" }'const res = await fetch("https://api.pozy.ai/v1/webhooks/logs/redeliver", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.POZY_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ webhookId: "whk_4Tq9Lm2Vx8Rb6Nc1Pz7K", eventId: "8e0b6f8a-3c51-4b7e-9d0a-2f4c6e1b9a77" }),
});
if (!res.ok) throw new Error((await res.json()).error);requests.post(
"https://api.pozy.ai/v1/webhooks/logs/redeliver",
headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
json={"webhookId": "whk_4Tq9Lm2Vx8Rb6Nc1Pz7K", "eventId": "8e0b6f8a-3c51-4b7e-9d0a-2f4c6e1b9a77"},
timeout=30,
).raise_for_status()- A redelivery is a new delivery with a new
X-Pozy-Delivery-Id. If it fails, it follows the normal retry schedule. - The event id stays the same, so your handler recognises an event it already processed.
- You can redeliver to any endpoint in the workspace, not only the one that received it first. That is a simple way to backfill a new endpoint with recent events. The endpoint's event and profile filters are not applied to a redelivery.
To replay every failed delivery of an outage, page through GET /v1/webhooks/logs?status=failed&webhookId=... and redeliver each eventId, pacing the calls to stay inside your rate limit.
#Endpoints
| Endpoint | What it does |
|---|---|
GET /v1/webhooks/logs |
List webhook delivery logs |
POST /v1/webhooks/logs/redeliver |
Redeliver a webhook event |
PUT /v1/webhooks/settings |
Update webhook |
GET /v1/webhooks/settings |
List webhooks |