For your customers
Retries and the DLQ
How failed deliveries are retried, how long they wait in the dead letter queue, and how to send them again.
Servers go down and deploys happen. A message your endpoint couldn't take is retried automatically, then kept for 7 days so nothing is lost while you fix things.
The retry schedule
Each endpoint gets up to three attempts per message:
| Attempt | When |
|---|---|
| 1 | As soon as the message arrives |
| 2 | 3 seconds after the first failure |
| 3 | 10 seconds after the second failure |
If the third attempt fails too, the message moves to the dead letter queue (DLQ) for that endpoint. Endpoints are independent: one failing endpoint never holds back the others.
What counts as a failure
Any answer other than 2xx, including a redirect, or no complete answer within 10 seconds. Each failed attempt records why:
error_code | Meaning |
|---|---|
http_<status> | Your server answered that status, e.g. http_500 or http_302. |
timeout | No complete answer within 10 seconds. |
connection_refused | Nothing accepted the connection. |
dns_error | The host name didn't resolve. |
tls_error | The TLS handshake or certificate check failed. |
blocked_target | The host resolved to a private or reserved address. |
other | Any other error. |
Delivery statuses
Each message's delivery_status sums up its deliveries to all your endpoints:
| Status | Meaning |
|---|---|
pending | Queued, no failure yet. |
retrying | An attempt failed and attempts remain. |
delivered | Every endpoint answered 2xx. |
dead_lettered | At least one endpoint failed all its attempts. The message is in the DLQ. |
no_endpoint | There was no enabled endpoint to deliver to. |
The dead letter queue
Messages in the DLQ keep their payload for 7 days from when they arrived there (expires_at), then are deleted. List them, and their count, with filter[dlq]=true:
curl -g "https://api.houndsat.com/api/json/inbound_messages?filter[dlq]=true&page[count]=true&fields[inbound_message]=device_id,received_at,dead_lettered_at,expires_at" \
-H "Authorization: Bearer $TOKEN"Send a message again
Once your endpoint is fixed, an Owner or Admin retries messages from the portal or the API. A retry sends the message again, with three fresh attempts, to each endpoint that failed it and is still enabled; an endpoint that already accepted the message never gets it twice. Retry up to 500 at once in bulk.
curl -X PATCH https://api.houndsat.com/api/json/inbound_messages/3d9f1a7c-5e2b-4c80-9f64-a1b8e3d0c5f2/retry \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/vnd.api+json" \
-d '{"data": {"type": "inbound_message", "id": "3d9f1a7c-5e2b-4c80-9f64-a1b8e3d0c5f2", "attributes": {}}}'curl -X POST https://api.houndsat.com/api/json/inbound_messages/bulk/retry \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/vnd.api+json" \
-d '{"data": {"ids": ["3d9f1a7c-5e2b-4c80-9f64-a1b8e3d0c5f2", "8e4a2c1f-0b7d-4f39-a6e5-c2d1b9f0e3a7"]}}'A retry is refused with 422 retry_refused, and meta.reason says why:
meta.reason | Why |
|---|---|
expired | The 7 days have passed. |
not_in_dlq | The message isn't in the DLQ (it was delivered, or is still being retried). |
no_enabled_endpoint | The endpoint that failed it is disabled or deleted. Enable it, then retry. |
still_running | Its last delivery is still finishing. Try again in a moment. |
{
"errors": [
{
"status": "422",
"code": "retry_refused",
"detail": "This message expired on Oct 12, 2026 14:03 UTC and can no longer be retried.",
"source": { "pointer": "/data/attributes/expires_at" },
"meta": { "reason": "expired" }
}
]
}Attempt history
Every attempt is recorded with its status code, error and duration, which is the quickest way to see why an endpoint is failing:
curl -g "https://api.houndsat.com/api/json/delivery_attempts?filter[inbound_message_id]=3d9f1a7c-5e2b-4c80-9f64-a1b8e3d0c5f2" \
-H "Authorization: Bearer $TOKEN"{
"type": "delivery_attempt",
"id": "…",
"attributes": {
"attempt_number": 2,
"attempted_at": "2026-10-12T14:03:27Z",
"succeeded": false,
"http_status": 503,
"error_code": "http_503",
"duration_ms": 412,
"webhook_endpoint_id": "e2b7d9f0-4c1a-4e63-b8a5-7d0f2c9e1b36",
…
}
}Attempt numbers continue across retries, so a message retried from the DLQ shows attempts 4, 5 and 6.