Retries and the event log
What happens when your endpoint is down, and how to catch up afterwards.
A delivery succeeds when your endpoint answers with any 2xx status within 10 seconds. Anything else is a failure: a timeout, a connection error, a 3xx redirect, a 4xx or a 5xx.
Retry schedule
Failed deliveries are retried with growing gaps:
| Attempt | After the previous one |
|---|---|
| 1 | Immediately |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
| 6 | 6 hours |
| 7 | 12 hours |
| 8 | 24 hours |
That's eight attempts over about 45 hours. After the last one fails, the event is marked failed and isn't retried again, but you can redeliver it.
Every attempt is signed afresh with a new timestamp, so retried events always pass the 5-minute replay check.
The event log
Every event from the last 30 days is listed with its delivery history:
curl "https://api.softlinksms.co.ke/api/v1/webhook-events?status=failed" \
-H "X-Api-Key: $SOFTLINK_API_KEY"{
"next": null,
"previous": null,
"results": [
{
"id": "evt_6a1f0c2e9b8d4f3aa1c27e5d9b0f4e21",
"object": "webhook_event",
"type": "sms.delivered",
"status": "failed",
"url": "https://example.com/webhooks/softlinksms",
"attempts": 8,
"last_status_code": 503,
"last_error": "HTTP 503",
"last_attempt_at": "2026-10-21T05:17:40Z",
"next_attempt_at": null,
"delivered_at": null,
"created_at": "2026-10-19T08:15:02Z",
"payload": { "id": "evt_6a1f0c2e9b8d4f3aa1c27e5d9b0f4e21", "type": "sms.delivered", "...": "..." }
}
]
}Filter by type (e.g. sms.failed) or status (pending, delivered or failed). last_error says what went wrong on the most recent attempt: an HTTP status, a timeout, or a connection error.
Redeliver an event
After an outage, replay what you missed:
curl -X POST https://api.softlinksms.co.ke/api/v1/webhook-events/evt_6a1f0c2e9b8d4f3aa1c27e5d9b0f4e21/redeliver \
-H "X-Api-Key: $SOFTLINK_API_KEY"The event is sent again straight away with the same id, so the deduplication in your handler still applies. Redeliver works on any event in the log, including ones already delivered.
Catching up after downtime
List status=failed events, redeliver each one, and let your handler's deduplication on id skip anything you'd already processed.