DocumentationMonitor, retry and recover deliveries

Monitor, retry and recover deliveries

Operate the Webhook Relay Outbound Gateway: delivery history, retrying one delivery, recovering failed deliveries, replaying skipped messages, endpoint health, payload Functions, limits and MCP tools.

The Outbound Gateway records every message, delivery and attempt for 35 days. You can see what a customer's endpoint answered, find every failing endpoint across all customers, retry one delivery, re-send everything that failed during an outage, or replay messages skipped while an endpoint was paused. None of this requires publishing the event again, and every copy keeps the original webhook-id. Everything on this page is also available in the dashboard's Outbound webhooks page, in the TypeScript and Go SDKs, and through MCP. For a product overview, see the Outbound Gateway.

Inspect delivery history

QuestionCall
What did we publish?GET /v1/outbound/messages?consumer=&event_type=&limit=&offset= (newest first, without payloads)
Where did one message go?GET /v1/outbound/messages/{id}: the payload plus one delivery per endpoint, with attempts
What did one endpoint receive?GET /v1/outbound/endpoints/{id}/deliveries

Filter deliveries

Filter an endpoint's deliveries with status (sent, failed, stalled, received or rejected), event_type or message_id, and page with limit (up to 100) and offset. The filters combine, so "failed invoice.paid deliveries to this endpoint" is one call. Each attempt records its status code, duration, response headers (up to 8 KiB) and the start of the response body (4 KiB per attempt; the latest response is kept up to 120 KiB). Custom endpoint headers are left out of the recorded request because they can hold credentials.

Delivery statusMeaning
receivedQueued; the first attempt hasn't been made yet.
stalledAn attempt failed and a retry is scheduled.
sentThe endpoint answered 2xx.
failedRetries ran out, the endpoint answered 410, or the endpoint's Function failed.
rejectedSkipped because the endpoint was paused or disabled, or dropped by a Function.
curl --user "$RELAY_KEY:$RELAY_SECRET" \
  "https://my.webhookrelay.com/v1/outbound/endpoints/6f0c2c0e-5a3b-4f43-9d0a-4c4f7d7a2b11/deliveries?status=failed&event_type=invoice.paid&limit=20"

The same filters in the SDKs (TypeScript @webhookrelay/sdk 0.3.0 or later, Go webhookrelay-go 0.7.0 or later):

const failed = await relay.outbound.endpoints.deliveries(endpointId, { status: "failed", event_type: "invoice.paid", limit: 20 });
const one = await relay.outbound.endpoints.deliveries(endpointId, { message_id: messageId });
failed, err := api.ListOutboundDeliveries(&webhookrelay.OutboundDeliveryListOptions{
    EndpointID: endpointID,
    Status:     "failed",
    EventType:  "invoice.paid",
    Limit:      20,
})

MCP: list_outbound_deliveries takes the same filters.

In the dashboard

Each message has its own page: a summary card (consumer, message ID, when it was received) beside the payload, then every endpoint it went to, by name, with the full attempt history and each response. From there you can retry a delivery. The publish dialog prefills the chosen event type's example payload as a template, and the create dialogs for consumers, event types and endpoints show the equivalent SDK call next to the form, so you can copy it into your code.

Retry one delivery

POST /v1/outbound/endpoints/{id}/retry with a message_id re-sends that message to that endpoint in the background and returns a recovery task. If the delivery is waiting for its next scheduled retry, it is sent now. A delivery whose first attempt is still queued is refused with 409.

curl --user "$RELAY_KEY:$RELAY_SECRET" \
  -H 'Content-Type: application/json' \
  -X POST https://my.webhookrelay.com/v1/outbound/endpoints/6f0c2c0e-5a3b-4f43-9d0a-4c4f7d7a2b11/retry \
  -d '{ "message_id": "0199cf5e-7a3c-7d2e-9b1a-3f4e5d6c7b8a" }'

Recover failed deliveries and replay missed ones

Two recovery tasks walk an endpoint's messages from since (RFC 3339, defaulting to 24 hours ago, at most 35 days back) until now:

  • Recover (POST /v1/outbound/endpoints/{id}/recover) re-sends deliveries that ran out of retries, typically after the customer fixes an outage.
  • Replay missing (POST /v1/outbound/endpoints/{id}/replay-missing) sends deliveries that were skipped while the endpoint was paused or disabled. Resume the endpoint first.
curl --user "$RELAY_KEY:$RELAY_SECRET" \
  -H 'Content-Type: application/json' \
  -X POST https://my.webhookrelay.com/v1/outbound/endpoints/6f0c2c0e-5a3b-4f43-9d0a-4c4f7d7a2b11/recover \
  -d '{ "since": "2026-10-08T00:00:00Z" }'

Each call answers 202 with a task. Follow its progress with GET /v1/outbound/recovery-tasks/{id}. status moves from pending to running to completed (or failed with an error), and processed counts the deliveries handled so far. A re-sent delivery starts a fresh retry schedule and keeps its earlier attempts in the history. An account can have up to 10 active recovery tasks.

The SDK equivalents:

const task = await relay.outbound.endpoints.recover(endpointId, { since: "2026-10-08T00:00:00Z" });
await relay.outbound.endpoints.resume(endpointId);
await relay.outbound.endpoints.replayMissing(endpointId);
await relay.outbound.endpoints.retry(endpointId, messageId);
const progress = await relay.outbound.recoveryTasks.get(task.id);
task, err := api.RecoverOutboundDeliveries(endpointID, time.Now().Add(-48*time.Hour))
_, err = api.ResumeOutboundEndpoint(endpointID)
_, err = api.ReplayMissingOutboundDeliveries(endpointID, time.Time{}) // zero time: last 24 hours
_, err = api.RetryOutboundDelivery(endpointID, messageID)
progress, err := api.GetOutboundRecoveryTask(task.ID)

Watch endpoint health

With many customers, some endpoints will always be failing, so health is a list you check, not an alert. The gateway tracks it on each endpoint: state, consecutive_failures, failing_since and stats (attempts and failures over the last 24 hours). An endpoint becomes failing after 5 failed attempts in a row, or when one delivery runs out of retries, and returns to active on the next success. Failing endpoints never open incidents or send email to your account.

  • GET /v1/outbound/health counts endpoints by state and reports the account's attempts and failures over the last 24 hours (updated within about 15 seconds).
  • GET /v1/outbound/endpoints?state=failing lists failing endpoints across all consumers, longest failing first. Add consumer to narrow it down.
curl --user "$RELAY_KEY:$RELAY_SECRET" https://my.webhookrelay.com/v1/outbound/health
curl --user "$RELAY_KEY:$RELAY_SECRET" "https://my.webhookrelay.com/v1/outbound/endpoints?state=failing&limit=20"
const { endpoints, stats } = await relay.outbound.health();
const failing = await relay.outbound.endpoints.listAll({ state: "failing", limit: 20 });
health, err := api.GetOutboundHealth() // health.Endpoints["failing"], health.Stats.Failures
failing, err := api.ListAllOutboundEndpoints(&webhookrelay.OutboundEndpointListOptions{
    State: webhookrelay.OutboundEndpointStateFailing,
    Limit: 20,
})

MCP: get_outbound_health, and list_outbound_endpoints with a state. The dashboard shows the same data on the Endpoint health tab: the counts by state and the failing endpoints across all consumers, longest failing first. Use POST /v1/outbound/endpoints/{id}/pause to stop deliveries to an endpoint your customer is migrating, and .../resume to start them again. See endpoint states for how endpoints move between active, failing, paused and disabled.

Transform or drop payloads with Functions

Attach a Function to an endpoint with function_id to tailor what one customer receives. The Function gets the published JSON payload as r.body, and it can:

  • replace the body with r.setBody(...). The result must be valid JSON up to 256 KiB.
  • drop the message with r.stopForwarding(). The delivery is recorded as rejected, "Message dropped by Function".
const invoice = JSON.parse(r.body)

if (invoice.amount === 0) {
  r.stopForwarding()
} else {
  r.setBody(JSON.stringify({ id: invoice.invoice_id, total: invoice.amount / 100, currency: "USD" }))
}

Outbound Functions can change only the body. The method, URL and headers belong to the endpoint, so a Function that changes them fails the delivery with an explanatory error. The Function runs once per message and endpoint, and its result is reused for every retry and replay. The Function attached when a message is published is the one that runs for it. If a worker crashes before the result is saved, the Function can run again, so avoid external side effects.

Limits

Your plan sets how many customers, endpoints and event types you can register, and how many messages you can publish each month. Outbound webhooks are on every plan, Free included.

PlanConsumersEndpoints per consumerEvent typesMessages / month
Free50520050,000
Basic10,00010300100,000
BusinessUnlimited255001,000,000
ProUnlimited1001,0005,000,000
EnterpriseCustomCustomCustomCustom

A message is one accepted publish. It counts once however many endpoints receive it, retries and replays don't count again, a duplicate publish with the same idempotency key doesn't count, a test event sent to one endpoint doesn't count, and a payload up to 256 KiB is one message. Outbound messages don't count toward the inbound webhook quota. Creating a consumer, endpoint or event type beyond your plan's count is refused with 403.

Monthly message quota and message packs

When the account has published its monthly quota, publishing returns 429 until the next billing period. Messages already accepted keep being delivered and retried. To keep publishing, add extra outbound message packs (recurring add-ons) or upgrade on the plan page:

PlanEach pack addsMonthlyYearly
Basic100,000 messages$5$54
Business1,000,000 messages$20$216
Pro5,000,000 messages$50$540

Enterprise plans can raise or remove any limit.

GET /v1/outbound/access (MCP get_outbound_access) returns your account's effective limits, with the message quota including packs, and this billing period's usage. The other limits apply to every plan:

LimitValue
PayloadJSON, 256 KiB
Event types per endpoint100 (or *)
EndpointsOne per URL per consumer
Custom headers32 per endpoint
Consumer IDs, event type names, idempotency keys, event_idUp to 128 characters
Rate50 deliveries per second per endpoint by default, up to 1,000; up to 16 in flight at once (or the rate, if lower)
Attempt timeout15 seconds by default, up to 60
Retry windowUp to 8 attempts within 48 hours
Messages awaiting preparation10,000 per account (429 beyond that)
Messages per monthPlan quota plus packs (429 beyond that until the next billing period)
Test events100 per account in any 24 hours, not counted as messages
Active recovery tasks10 per account
Recorded responseHeaders up to 8 KiB per attempt; body 4 KiB per attempt, 120 KiB for the latest response
History35 days

MCP tools and the dashboard agent

Every REST operation has an MCP tool with the same name. The dashboard agent uses the same tools, so you can ask it things like "which endpoints are failing?" or "recover customer_42's failed deliveries since Monday". Tools follow your role and any outbound token restriction, and configuration, secret and recovery changes are audited the same way as REST calls. create_outbound_endpoint accepts an optional secret to import an existing signing secret.

AreaTools
Access and limitsget_outbound_access
Consumerslist_outbound_consumers, get_outbound_consumer, upsert_outbound_consumer, delete_outbound_consumer
Event typeslist_outbound_event_types, save_outbound_event_type, delete_outbound_event_type
Endpointslist_outbound_endpoints, get_outbound_endpoint, create_outbound_endpoint, update_outbound_endpoint, delete_outbound_endpoint, pause_outbound_endpoint, resume_outbound_endpoint, rotate_outbound_endpoint_secret, reveal_outbound_endpoint_secret, get_outbound_health
Messagespublish_outbound_message, send_outbound_test, list_outbound_messages, get_outbound_message
Deliveries and recoverylist_outbound_deliveries, retry_outbound_delivery, recover_outbound_deliveries, replay_missing_outbound_deliveries, get_outbound_recovery_task

Restrict tokens to outbound

Give each service and agent the smallest outbound permission it needs. A token with permissions.outbound set reaches only /v1/outbound/* and the outbound MCP tools, whatever its role, and its role is still an upper bound:

permissions.outboundAllows
readConfiguration, messages, deliveries and health
publishPublishing messages only
manageEvery outbound operation, including endpoints, secrets and recovery

Create one on the Access Tokens page (the Outbound option) or with the token API; see Create an access token.

Moving from another provider? Each migration guide links a machine-readable playbook under https://webhookrelay.com/outbound-migration/ that a coding agent can follow with these tools, up to a human approval step before any traffic moves.

Frequently asked questions

How do I resend outbound webhooks after a customer's outage?

Once the endpoint is reachable again, call POST /v1/outbound/endpoints/{id}/recover with an optional since timestamp (the default is the last 24 hours, the maximum is 35 days). Webhook Relay re-sends that endpoint's deliveries that ran out of retries, in the background, with the original webhook-id. If the endpoint was paused or disabled instead, resume it and call replay-missing.

How do I find failing endpoints across all my customers?

Call GET /v1/outbound/endpoints?state=failing. It lists failing endpoints across every consumer, longest failing first, and you can add consumer to narrow it to one customer. GET /v1/outbound/health counts all endpoints by state and reports the account's attempts and failures over the last 24 hours. The dashboard shows both on the Endpoint health tab, and the SDKs expose them as relay.outbound.endpoints.listAll and relay.outbound.health in TypeScript, or ListAllOutboundEndpoints and GetOutboundHealth in Go.

What are the Outbound Gateway limits on each plan?

Free allows 50 consumers, 5 endpoints per consumer, 200 event types and 50,000 messages a month. Basic allows 10,000 consumers, 10 endpoints per consumer, 300 event types and 100,000 messages. Business allows unlimited consumers, 25 endpoints per consumer, 500 event types and 1 million messages. Pro allows unlimited consumers, 100 endpoints per consumer, 1,000 event types and 5 million messages. A message is one accepted publish, so retries and fan-out are free. Over the monthly quota, publishing returns 429 until the next billing period unless you add message packs: +100,000 for $5 a month on Basic, +1 million for $20 on Business, +5 million for $50 on Pro. Enterprise plans can raise or remove any limit.

What happens when I reach the monthly outbound message quota?

Publishing is refused with 429 Too Many Requests until the next billing period starts. Messages already accepted keep being delivered and retried. To keep publishing, add an extra outbound message pack or upgrade on the plan page in the dashboard. GET /v1/outbound/access shows the quota (limits.messages) and this period's count (usage.messages).

How long is outbound delivery history kept?

Messages, deliveries with their attempts, and recovery tasks are kept for 35 days. Retries, recovery and replay work within that window.

Can I change the payload for one customer?

Yes. Attach a Function to that customer's endpoint with function_id. The Function receives the published JSON payload and can return a different JSON body (up to 256 KiB) or drop the message. It runs once per message and endpoint, and the result is reused for every retry.

Can an AI agent manage outbound webhooks?

Yes. Every Outbound Gateway REST operation has an MCP tool with the same name, such as publish_outbound_message, list_outbound_deliveries or recover_outbound_deliveries. The tools are available to MCP clients and to the agent in the Webhook Relay dashboard, under the same permissions and audit log as the API.

Did this page help you?