Skip to content
yeetpostyeetpost

Webhooks

Register an endpoint, read the delivery log, replay, send a test event.


Delivery of post.sent and post.failed events to your own https endpoint.

Register a webhook

POST/webhooks

Registers an https endpoint of yours to receive post.sent and post.failed events.

The URL must be https and must point at a public host: private, loopback, link-local and metadata addresses are rejected. Deliveries do not follow redirects, so register the final URL.

The signing secret is returned exactly once, in this response. Store it somewhere safe; there is no way to read it again, only to delete the webhook and register a new one.

Rate limit: 60 requests per minute per API key, shared with GET /webhooks.

curl -X POST "https://api.yeetpost.com/api/v2/webhooks" \  -H "x-api-key: $YEETPOST_API_KEY" \  -H "content-type: application/json" \  -d '{  "url": "https://example.com/hooks/yeetpost",  "events": [    "post.sent",    "post.failed"  ]}'
response 200
{  "webhook": {    "id": "2c9a4f3b-77d1-4b0e-9f2a-8c3e1a5b7d90",    "url": "https://example.com/hooks/yeetpost",    "events": [      "post.sent",      "post.failed"    ],    "secret": "yp_whsec_3f1a...",    "createdAt": "2026-08-26T09:24:11.412Z"  }}

request body (application/json)

urlstring <uri>required

Where to POST events. Must be https and must point at a public host.

eventsstring[], at least 1 itemoptional

Events to subscribe to. Defaults to all of them.

post.sent | post.failed | post.deleted | post.metrics | post.rescheduled | connection.created | connection.removed | connection.reconnect_required

response 200

The webhook was registered. This is the only response that contains the secret.

headers: x-request-id, x-ratelimit-limit, x-ratelimit-remaining, x-ratelimit-reset

response body

webhookobjectrequired
webhook.idstring <uuid>required
webhook.urlstring <uri>required
webhook.eventsstring[]required

post.sent | post.failed | post.deleted | post.metrics | post.rescheduled | connection.created | connection.removed | connection.reconnect_required

webhook.createdAtstring <date-time>required

An ISO 8601 timestamp in UTC, in the same format as the timestamps you send in: 2026-08-26T09:24:11.412Z.

webhook.secretstringrequired

The signing secret, yp_whsec_…. Shown only here, only once.

errors

Missing url, a non-https url, a url pointing at a non-public host, or an unknown event name. A body that is not valid JSON is rejected before the rate limit is checked, so that one response carries x-request-id and no rate limit headers.

Missing or invalid API key.

More than 60 requests in a minute for this API key. Back off and retry.

Something went wrong on our side. Quote the x-request-id when you report it. A failure can happen before the rate limit is checked, so this response carries the request id and nothing else.

What each code means and the fix: errors.

List your webhooks

GET/webhooks

Your webhooks, newest first. Secrets are never included.

Rate limit: 60 requests per minute per API key, shared with POST /webhooks.

curl "https://api.yeetpost.com/api/v2/webhooks" \  -H "x-api-key: $YEETPOST_API_KEY"
response 200
{  "webhooks": [    {      "id": "2c9a4f3b-77d1-4b0e-9f2a-8c3e1a5b7d90",      "url": "https://example.com/hooks/yeetpost",      "events": [        "post.sent",        "post.failed"      ],      "createdAt": "2026-08-26T09:24:11.412Z"    }  ]}

response 200

Your webhooks.

headers: x-request-id, x-ratelimit-limit, x-ratelimit-remaining, x-ratelimit-reset

response body

webhooksobject[]required
webhooks[].idstring <uuid>required
webhooks[].urlstring <uri>required
webhooks[].eventsstring[]required

post.sent | post.failed | post.deleted | post.metrics | post.rescheduled | connection.created | connection.removed | connection.reconnect_required

webhooks[].createdAtstring <date-time>required

An ISO 8601 timestamp in UTC, in the same format as the timestamps you send in: 2026-08-26T09:24:11.412Z.

errors

Missing or invalid API key.

More than 60 requests in a minute for this API key. Back off and retry.

Something went wrong on our side. Quote the x-request-id when you report it. A failure can happen before the rate limit is checked, so this response carries the request id and nothing else.

What each code means and the fix: errors.

Delete a webhook

DELETE/webhooks/{webhookId}

Stops delivering events to this webhook. Deliveries that are still queued for it are marked failed.

Rate limit: 60 requests per minute per API key.

curl -X DELETE "https://api.yeetpost.com/api/v2/webhooks/2c9a4f3b-77d1-4b0e-9f2a-8c3e1a5b7d90" \  -H "x-api-key: $YEETPOST_API_KEY"
response 200
{  "id": "2c9a4f3b-77d1-4b0e-9f2a-8c3e1a5b7d90",  "deleted": true}

path parameters

webhookIdstring <uuid>required

Id of the webhook, as returned by POST /webhooks or GET /webhooks.

response 200

The webhook is deleted.

headers: x-request-id, x-ratelimit-limit, x-ratelimit-remaining, x-ratelimit-reset

response body

idstring <uuid>required
deletedbooleanrequired

true

errors

Malformed webhook id.

Missing or invalid API key.

No webhook of yours with that id.

More than 60 requests in a minute for this API key. Back off and retry.

Something went wrong on our side. Quote the x-request-id when you report it. A failure can happen before the rate limit is checked, so this response carries the request id and nothing else.

What each code means and the fix: errors.

List a webhook's deliveries

GET/webhooks/{webhookId}/deliveries

The delivery log for one webhook of yours, newest first: what was sent, how it went, and the last error when it did not. Use it to see why a receiver is not getting events.

Deliveries are kept for 30 days. A dead letter's 30 days run from when it died rather than from when it was queued, so a delivery that spent its last day retrying is still there to replay.

Rate limit: 60 requests per minute per API key.

curl "https://api.yeetpost.com/api/v2/webhooks/2c9a4f3b-77d1-4b0e-9f2a-8c3e1a5b7d90/deliveries" \  -H "x-api-key: $YEETPOST_API_KEY"
response 200
{  "deliveries": [    {      "id": "8d3f2b7c-4e1a-4c6d-9b0f-1a2b3c4d5e6f",      "eventId": "8d3f2b7c-4e1a-4c6d-9b0f-1a2b3c4d5e6f",      "event": "post.sent",      "status": "delivered",      "attempts": 1,      "createdAt": "2026-08-26T09:24:11.412Z",      "nextAttemptAt": "2026-08-26T09:24:11.412Z",      "deliveredAt": "2026-08-26T09:24:12.004Z",      "deadAt": null,      "lastStatusCode": 200,      "lastError": null    },    {      "id": "1c4e5f6a-7b8c-4d9e-8f0a-1b2c3d4e5f60",      "eventId": "1c4e5f6a-7b8c-4d9e-8f0a-1b2c3d4e5f60",      "event": "post.failed",      "status": "dead",      "attempts": 7,      "createdAt": "2026-08-25T18:02:44.118Z",      "nextAttemptAt": "2026-08-26T14:39:12.882Z",      "deliveredAt": null,      "deadAt": "2026-08-26T14:39:13.204Z",      "lastStatusCode": 500,      "lastError": "response status 500"    }  ],  "limit": 25,  "hasMore": false}

path parameters

webhookIdstring <uuid>required

Id of the webhook, as returned by POST /webhooks or GET /webhooks.

query parameters

limitinteger, 1 to 100, default 25optional

How many deliveries to return.

statusstringoptional

Only deliveries in this state.

pending | delivered | failed | dead

startingAfterstring <uuid>optional

Id of the last delivery of the previous page. New deliveries land at the top of this list while you page it, so the next page is the deliveries older than that one rather than the ones past an offset.

response 200

The webhook's deliveries, newest first.

headers: x-request-id, x-ratelimit-limit, x-ratelimit-remaining, x-ratelimit-reset

response body

deliveriesobject[]required
deliveries[].idstring <uuid>required

The same id the delivery carries in its x-yeetpost-delivery header.

deliveries[].eventIdstring <uuid>required

Id of the event, the same value as id. It is what the delivery carries in x-yeetpost-event-id and in the eventId field of its body, on every attempt and every replay. Deduplicate on it.

deliveries[].eventstringrequired

post.sent | post.failed | post.deleted | post.metrics | post.rescheduled | connection.created | connection.removed | connection.reconnect_required

deliveries[].statusstringrequired

pending is waiting for its next attempt, processing is being sent right now, failed means the webhook was deleted under it, dead ran out of attempts and is only sent again by a replay.

pending | processing | delivered | failed | dead

deliveries[].attemptsintegerrequired

How many times it has been sent. Up to 7, and back to 0 after a replay.

deliveries[].createdAtstring <date-time>required

An ISO 8601 timestamp in UTC, in the same format as the timestamps you send in: 2026-08-26T09:24:11.412Z.

deliveries[].nextAttemptAtstring <date-time>required

An ISO 8601 timestamp in UTC, in the same format as the timestamps you send in: 2026-08-26T09:24:11.412Z.

deliveries[].deliveredAtstring <date-time> | nullrequired

Same format as Timestamp, or null.

deliveries[].deadAtstring <date-time> | nullrequired

Same format as Timestamp, or null.

deliveries[].lastStatusCodeinteger | nullrequired

The status your receiver answered the last attempt with, or null when the request never got a response.

deliveries[].lastErrorstring | nullrequired

Our own one-line summary of why the last attempt did not land, or null. Never your receiver's response body.

limitintegerrequired
hasMorebooleanrequired

Whether there is another page. Ask for it with startingAfter set to the id of the last delivery here.

errors

Malformed webhook id, a limit outside 1 to 100, an unknown status, or a startingAfter that is not a delivery of this webhook.

Missing or invalid API key.

No webhook of yours with that id.

More than 60 requests in a minute for this API key. Back off and retry.

Something went wrong on our side. Quote the x-request-id when you report it. A failure can happen before the rate limit is checked, so this response carries the request id and nothing else.

What each code means and the fix: errors.

Replay a delivery

POST/webhooks/{webhookId}/deliveries/{deliveryId}/replay

Sends one delivery again. It is a new attempt chain on the same event: the same eventId, the same body, the same signature rules, so a receiver that deduplicates on the event id sees the event it missed rather than a new one.

A dead, failed or delivered delivery can be replayed. One that is still pending or processing is refused: it is already on its way.

Rate limit: 60 requests per minute per API key.

curl -X POST "https://api.yeetpost.com/api/v2/webhooks/2c9a4f3b-77d1-4b0e-9f2a-8c3e1a5b7d90/deliveries/1c4e5f6a-7b8c-4d9e-8f0a-1b2c3d4e5f60/replay" \  -H "x-api-key: $YEETPOST_API_KEY"
response 200
{  "delivery": {    "id": "1c4e5f6a-7b8c-4d9e-8f0a-1b2c3d4e5f60",    "eventId": "1c4e5f6a-7b8c-4d9e-8f0a-1b2c3d4e5f60",    "event": "post.failed",    "status": "pending",    "attempts": 0,    "createdAt": "2026-08-25T18:02:44.118Z",    "nextAttemptAt": "2026-08-27T08:11:02.551Z",    "deliveredAt": null,    "deadAt": null,    "lastStatusCode": null,    "lastError": null  }}

path parameters

webhookIdstring <uuid>required

Id of the webhook, as returned by POST /webhooks or GET /webhooks.

deliveryIdstring <uuid>required

Id of the delivery, as returned by GET /webhooks/{webhookId}/deliveries.

response 200

The delivery is queued for a fresh attempt chain.

headers: x-request-id, x-ratelimit-limit, x-ratelimit-remaining, x-ratelimit-reset

response body

deliveryobjectrequired

One attempt series at delivering one event to one webhook.

delivery.idstring <uuid>required

The same id the delivery carries in its x-yeetpost-delivery header.

delivery.eventIdstring <uuid>required

Id of the event, the same value as id. It is what the delivery carries in x-yeetpost-event-id and in the eventId field of its body, on every attempt and every replay. Deduplicate on it.

delivery.eventstringrequired

post.sent | post.failed | post.deleted | post.metrics | post.rescheduled | connection.created | connection.removed | connection.reconnect_required

delivery.statusstringrequired

pending is waiting for its next attempt, processing is being sent right now, failed means the webhook was deleted under it, dead ran out of attempts and is only sent again by a replay.

pending | processing | delivered | failed | dead

delivery.attemptsintegerrequired

How many times it has been sent. Up to 7, and back to 0 after a replay.

delivery.createdAtstring <date-time>required

An ISO 8601 timestamp in UTC, in the same format as the timestamps you send in: 2026-08-26T09:24:11.412Z.

delivery.nextAttemptAtstring <date-time>required

An ISO 8601 timestamp in UTC, in the same format as the timestamps you send in: 2026-08-26T09:24:11.412Z.

delivery.deliveredAtstring <date-time> | nullrequired

Same format as Timestamp, or null.

delivery.deadAtstring <date-time> | nullrequired

Same format as Timestamp, or null.

delivery.lastStatusCodeinteger | nullrequired

The status your receiver answered the last attempt with, or null when the request never got a response.

delivery.lastErrorstring | nullrequired

Our own one-line summary of why the last attempt did not land, or null. Never your receiver's response body.

errors

Malformed ids, or a delivery that is already queued for another attempt.

Missing or invalid API key.

No webhook of yours with that id, or no delivery of that webhook with that id.

More than 60 requests in a minute for this API key. Back off and retry.

Something went wrong on our side. Quote the x-request-id when you report it. A failure can happen before the rate limit is checked, so this response carries the request id and nothing else.

What each code means and the fix: errors.

Send a test event

POST/webhooks/{webhookId}/test

Queues a post.sent delivery with a fixed, obviously fake post in it and "test": true at the top level of the body, so you can check a receiver and its signature check without posting anything.

It goes out through the normal worker, signed and retried like any other delivery, and it shows up in the delivery log. The webhook gets it whatever it is subscribed to, because you asked for it by id.

Rate limit: 60 requests per minute per API key.

curl -X POST "https://api.yeetpost.com/api/v2/webhooks/2c9a4f3b-77d1-4b0e-9f2a-8c3e1a5b7d90/test" \  -H "x-api-key: $YEETPOST_API_KEY"
response 200
{  "delivery": {    "id": "3a1d9c02-5b6e-4f7a-8c1d-9e0f1a2b3c4d",    "eventId": "3a1d9c02-5b6e-4f7a-8c1d-9e0f1a2b3c4d",    "event": "post.sent",    "status": "pending",    "attempts": 0,    "createdAt": "2026-08-27T08:11:02.551Z",    "nextAttemptAt": "2026-08-27T08:11:02.551Z",    "deliveredAt": null,    "deadAt": null,    "lastStatusCode": null,    "lastError": null  }}

path parameters

webhookIdstring <uuid>required

Id of the webhook, as returned by POST /webhooks or GET /webhooks.

response 200

The test delivery is queued.

headers: x-request-id, x-ratelimit-limit, x-ratelimit-remaining, x-ratelimit-reset

response body

deliveryobjectrequired

One attempt series at delivering one event to one webhook.

delivery.idstring <uuid>required

The same id the delivery carries in its x-yeetpost-delivery header.

delivery.eventIdstring <uuid>required

Id of the event, the same value as id. It is what the delivery carries in x-yeetpost-event-id and in the eventId field of its body, on every attempt and every replay. Deduplicate on it.

delivery.eventstringrequired

post.sent | post.failed | post.deleted | post.metrics | post.rescheduled | connection.created | connection.removed | connection.reconnect_required

delivery.statusstringrequired

pending is waiting for its next attempt, processing is being sent right now, failed means the webhook was deleted under it, dead ran out of attempts and is only sent again by a replay.

pending | processing | delivered | failed | dead

delivery.attemptsintegerrequired

How many times it has been sent. Up to 7, and back to 0 after a replay.

delivery.createdAtstring <date-time>required

An ISO 8601 timestamp in UTC, in the same format as the timestamps you send in: 2026-08-26T09:24:11.412Z.

delivery.nextAttemptAtstring <date-time>required

An ISO 8601 timestamp in UTC, in the same format as the timestamps you send in: 2026-08-26T09:24:11.412Z.

delivery.deliveredAtstring <date-time> | nullrequired

Same format as Timestamp, or null.

delivery.deadAtstring <date-time> | nullrequired

Same format as Timestamp, or null.

delivery.lastStatusCodeinteger | nullrequired

The status your receiver answered the last attempt with, or null when the request never got a response.

delivery.lastErrorstring | nullrequired

Our own one-line summary of why the last attempt did not land, or null. Never your receiver's response body.

errors

Malformed webhook id.

Missing or invalid API key.

No webhook of yours with that id.

More than 60 requests in a minute for this API key. Back off and retry.

Something went wrong on our side. Quote the x-request-id when you report it. A failure can happen before the rate limit is checked, so this response carries the request id and nothing else.

What each code means and the fix: errors.