Skip to content

Read one delivery's attempts

GET
/v1/webhooks/{webhookId}/deliveries/{deliveryId}/attempts
curl --request GET \
--url https://api.roundrobinbot.eu/v1/webhooks/example/deliveries/example/attempts \
--header 'Authorization: Bearer <token>'

The whole retry ladder of one delivery, oldest attempt first, each with the exact body and headers that attempt sent. The body is what was posted at the time, byte for byte, which is what a signature verifies over. It is not the rotation as it stands now: the payload carries state, so an event sent yesterday describes yesterday. Always one page, because a delivery has as many attempts as the ladder has rungs, so page and pageSize are not accepted here. No ETag: the rows are append-only and nothing here carries a version.

webhookId
required
string
deliveryId
required
string

OK

Media typeapplication/json

The envelope every /v1 collection comes back in.

object
data
Array<object>

One attempt with the exact bytes we sent it with.

object
at

When the attempt was made.

string format: date-time
attemptNumber

1 for the first send, incrementing over the retry ladder.

integer format: int32
deliveryId

The id your receiver deduplicates on. Minted once per event and identical across every attempt, so two calls carrying the same id are the same event and the second is safe to ignore.

string
durationMs

How long the attempt took, from the request leaving to the answer or the failure that ended it. Absent when no request was made.

null | integer format: int64
error

Why the attempt ended when no status code explains it, from a closed snake_case vocabulary: timeout, connection, secure_connection, name_resolution, redirect_not_followed, private_address and the rest. Never the receiver’s own answer, which the attempts route reports separately as a bounded response snippet.

null | string
eventType

The event type, as it appears in the payload: duty.changed and the rest.

string
nextAttemptDueAt

When the next attempt is due, if there is one. Absent when this attempt was delivered, when nothing more will be tried, or on a row recorded before this field existed.

null | string format: date-time
outcome

How the attempt ended.

string
requestBody

The exact bytes we POSTed.

string
requestHeaders

The headers we sent, including Round-Robin-Signature and Round-Robin-Timestamp, so the HMAC over the body above can be reproduced.

Array<object>

One header we sent, spelled exactly as it went out.

object
name
string
value
string
responseBodySnippet

A bounded snippet of what your server answered with, on an attempt that got an answer. Absent on a row where nothing was sent.

null | string
responseBodyTruncated

Whether your real answer was longer than the snippet above carries.

boolean
retryAfterSecondsRequested

What your Retry-After header asked for, in seconds, on a 429 or 503 answer. Absent otherwise. Not necessarily what the next attempt waited: the ladder clamps this, so a receiver can ask us to wait longer than it otherwise would, never to hurry it.

null | integer format: int32
rotationId

The rotation the event happened on.

string
round

Which round of the retry ladder this attempt belongs to, counting from 1: the first send and its in-process retries are round 1, then each scheduled redelivery step is its own round.

null | integer format: int32
statusCode

The status code your server answered with, absent when nothing answered. That absence is the whole difference between failed and no_response.

null | integer format: int32
totalRounds

How many rounds the retry ladder has today, so you never have to hard-code its shape.

integer format: int32
page
integer format: int32
pageSize
integer format: int32
totalItems
integer format: int64
totalPages
integer format: int32
Examplegenerated
{
"data": [
{
"at": "2026-04-15T12:00:00Z",
"attemptNumber": 1,
"deliveryId": "example",
"durationMs": 1,
"error": "example",
"eventType": "example",
"nextAttemptDueAt": "2026-04-15T12:00:00Z",
"outcome": "example",
"requestBody": "example",
"requestHeaders": [
{
"name": "example",
"value": "example"
}
],
"responseBodySnippet": "example",
"responseBodyTruncated": true,
"retryAfterSecondsRequested": 1,
"rotationId": "example",
"round": 1,
"statusCode": 1,
"totalRounds": 1
}
],
"page": 1,
"pageSize": 1,
"totalItems": 1,
"totalPages": 1
}

Bad Request

Media typeapplication/json
object
detail
null | string
instance
null | string
status
null | integer format: int32
title
null | string
type
null | string
Examplegenerated
{
"detail": "example",
"instance": "example",
"status": 1,
"title": "example",
"type": "example"
}

Unauthorized

Media typeapplication/json
object
detail
null | string
instance
null | string
status
null | integer format: int32
title
null | string
type
null | string
Examplegenerated
{
"detail": "example",
"instance": "example",
"status": 1,
"title": "example",
"type": "example"
}

Forbidden

Media typeapplication/json
object
detail
null | string
instance
null | string
status
null | integer format: int32
title
null | string
type
null | string
Examplegenerated
{
"detail": "example",
"instance": "example",
"status": 1,
"title": "example",
"type": "example"
}

Not Found

Media typeapplication/json
object
detail
null | string
instance
null | string
status
null | integer format: int32
title
null | string
type
null | string
Examplegenerated
{
"detail": "example",
"instance": "example",
"status": 1,
"title": "example",
"type": "example"
}