replay_limit_reached

This delivery has been replayed 3 times in the last hour. Wait for `next_replay_allowed_at` — and fix the receiving endpoint, which is what keeps failing.

HTTP 409The resource is not in a state that allows this operation.

When it fires

You called POST /v1/webhooks/{id}/deliveries/{delivery_id}/replay for a delivery that has already been replayed 3 times in the last 60 minutes. The window is rolling: it opens on the first replay and resets once it elapses, so a delivery you have not replayed for an hour starts fresh with all 3 again.

This is not a lifetime cap. A delivery whose endpoint was broken last week can still be replayed today, as long as it is inside the 7-day replay window.

The budget belongs to the original delivery. A replay produces a new delivery, and naming that new delivery in a replay call draws on the same budget. Chaining replays is not a way around the limit.

force: true does not raise the limit either. It only overrides the refusal to re-send a delivery that already succeeded, and it still consumes one of the 3.

Response shape

Every Repull error follows the same envelope. The code is stable and safe to switch on.

{
  "error": {
    "code": "replay_limit_reached",
    "message": "Delivery del_xyz789 has been replayed 3 times in the last hour.",
    "fix": "Wait until next_replay_allowed_at, then replay again. If the same delivery keeps failing, fix the receiving endpoint first — replaying will not change the result.",
    "docs_url": "https://repull.dev/docs/errors/replay_limit_reached",
    "request_id": "req_01J5X7Y8Z9ABCDEF12345678",
    "replays_made": 3,
    "replay_limit": 3,
    "next_replay_allowed_at": "2026-06-15T15:30:00Z"
  }
}

How to fix

  1. Read the `Retry-After` response header — the number of seconds until the window clears — or `next_replay_allowed_at` in the body for the same moment as an ISO-8601 timestamp.
  2. Do not retry before then, and do not fall back to replaying the delivery a previous replay produced: it shares the budget.
  3. Use the wait. Three failed replays in an hour means the receiver is what is broken, not the delivery. Check that your endpoint returns 2xx within the 10-second delivery timeout, and that your signature check is not rejecting valid requests.
  4. Read `GET /v1/webhooks/{id}/deliveries` — it records the status code and response body your endpoint returned for each failure, which is usually the fastest way to see what it is complaining about.
  5. Confirm the fix with `POST /v1/webhooks/{id}/test/{event_type}` before spending more replays. A test event does not count against the replay budget.
  6. Then replay the real delivery once, and read `replaysRemaining` on the 200 response so you know what is left.

Common gotchas

  • 409, not 429. The conflict is with the state of one delivery, not with the pace of your requests. Your key is not throttled: other endpoints keep working, and replays of other deliveries go through normally. Routing this into the same global backoff you use for rate_limited will stall work that was never blocked.
  • Retry-After is in seconds. Multiplying it by 1000 twice is the usual JavaScript slip.
  • Automatic retries already happened. Every failed delivery is retried 5 times over roughly 26 hours before you see it in the failed list. A manual replay is for after you have fixed something, not a way to try the same broken receiver again.
  • Track the budget from the success response. A 200 carries replayNumber, replaysRemaining and nextWindowResetAt. Reading those is cheaper than discovering the limit with a 409.

Examples

curl

# The 4th replay inside the window
curl -X POST https://api.repull.dev/v1/webhooks/wh_abc123/deliveries/del_xyz789/replay \
  -H "Authorization: Bearer sk_live_YOUR_KEY"
# HTTP/1.1 409 Conflict
# Retry-After: 2280
# { "error": { "code": "replay_limit_reached", "next_replay_allowed_at": "2026-06-15T15:30:00Z", ... } }

# What did the endpoint actually return on the failures?
curl "https://api.repull.dev/v1/webhooks/wh_abc123/deliveries?status=failed" \
  -H "Authorization: Bearer sk_live_YOUR_KEY"

# Confirm the endpoint is healthy again — test events do not use replay budget
curl -X POST https://api.repull.dev/v1/webhooks/wh_abc123/test/reservation.created \
  -H "Authorization: Bearer sk_live_YOUR_KEY"

# After the window clears, replay once more
curl -X POST https://api.repull.dev/v1/webhooks/wh_abc123/deliveries/del_xyz789/replay \
  -H "Authorization: Bearer sk_live_YOUR_KEY"
# HTTP/1.1 200 OK
# { "id": "del_new456", "replayNumber": 1, "replaysRemaining": 2, "nextWindowResetAt": "..." }

TypeScript

const API = 'https://api.repull.dev'
const headers = {
  Authorization: `Bearer ${process.env.REPULL_API_KEY}`,
  'Content-Type': 'application/json',
}

async function replayDelivery(webhookId: string, deliveryId: string) {
  const res = await fetch(
    `${API}/v1/webhooks/${webhookId}/deliveries/${deliveryId}/replay`,
    { method: 'POST', headers },
  )

  if (res.status === 409) {
    const { error } = await res.json()
    if (error.code === 'replay_limit_reached') {
      // Scoped to this delivery only — keep replaying other deliveries.
      // Do not chain onto the delivery a previous replay produced: same budget.
      const retryAfter = Number(res.headers.get('Retry-After') ?? 60)
      throw new ReplayBlocked(deliveryId, retryAfter, error.next_replay_allowed_at)
    }
  }

  const delivery = await res.json()
  if (delivery.replaysRemaining === 0) {
    console.warn(`No replays left for ${deliveryId} until ${delivery.nextWindowResetAt}`)
  }
  return delivery
}

class ReplayBlocked extends Error {
  constructor(
    readonly deliveryId: string,
    readonly retryAfterSeconds: number,
    readonly nextAllowedAt: string,
  ) {
    super(`Replay budget spent for ${deliveryId}; next attempt at ${nextAllowedAt}`)
  }
}

If you're an AI agent

This delivery has been replayed 3 times in the last hour. The limit is per delivery, not per API key — do NOT back off globally, and do not replay the delivery a previous replay produced, because it shares the same budget. Wait until next_replay_allowed_at (or Retry-After seconds). More importantly: three failed replays means the receiving endpoint is broken, so investigate that instead of queueing a 4th attempt. Read GET /v1/webhooks/{id}/deliveries for the response the endpoint returned, fix it, verify with POST /v1/webhooks/{id}/test/{event_type} (free), then replay once.

Hit an error that isn't covered? Email hello@repull.dev with the request id from the response headers.

AI