SolidHooksSolidHooks/ Docs

Replay and retry

Recover from a failed or missed delivery by retrying one delivery or replaying a whole event, and know which one costs quota.

SolidHooks gives you two ways to send something again. They look similar and behave very differently, so pick on purpose.

Retry re-sends one delivery to one destination. It reuses the same delivery row and is free. Replay takes a stored event and runs it through its connections again, as if it had just arrived. It creates new deliveries, and every new delivery is billable.

How they differ

RetryReplay
EndpointPOST /v1/deliveries/{id}/retryPOST /v1/events/{id}/replay
Acts onOne deliveryOne event, all its connections
Delivery rowsSame row, new attemptNew delivery per matching connection
BilledNoYes, one delivery each
Quota gateNone429 QUOTA_EXCEEDED on hard-capped plans at the limit
Uses current routingNo, same destination as beforeYes, connections that are active now
Permissiondelivery: retryevent: replay

Both are member-tier actions. A member, or an unscoped API key, can call them. A scoped key needs the matching grant.

Retry one delivery

Use retry when a destination was down or misbehaving and you want the same delivery sent again.

curl -s -X POST "$API/v1/deliveries/dlv_.../retry" \
  -H "authorization: Bearer $SDHK_KEY"

The response is { "ok": true }. The delivery goes back to pending and a worker picks it up. Its next attempt is recorded with trigger: "manual_retry", and that attempt also records who asked for it (actorType of user, api_key or agent). Only that first attempt carries manual_retry. If it fails, the attempts after it follow the normal retry schedule and are marked auto_retry.

Read the history with GET /v1/deliveries/{deliveryId}/attempts.

A delivery that is already queued returns 409

Retry is refused with 409 CONFLICT while the delivery still owns a live job. That includes a delivery with status: "throttled", which is waiting behind a rate limit and will send on its own, and one that is already scheduled for its next automatic attempt. Do not loop on the 409. Watch the delivery's status, or the queue through GET /v1/deliveries/stats, and retry once it has finished.

A retried delivery appears in the delivery stream when its next attempt starts, not at the moment you call retry.

curl -s -X POST "$API/v1/deliveries/dlv_.../retry" \
  -H "authorization: Bearer $SDHK_KEY"

Replay an event

Use replay when the event itself needs to go out again: you fixed a bug in the receiver and want to reprocess history, or you added a connection after the event arrived.

curl -s -X POST "$API/v1/events/evt_.../replay" \
  -H "authorization: Bearer $SDHK_KEY"
{ "ok": true, "deliveriesCreated": 2 }

Replay routes the stored event through every connection that is active now. It uses the event type saved at ingest, not the source's current eventTypeField, so connection filters apply exactly as they did the first time. If a connection has since been disabled or its filter no longer matches, it creates no delivery, and deliveriesCreated can be 0.

What can be replayed

  • A duplicate event is refused with 400 BAD_REQUEST. It never had deliveries of its own.
  • A rejected event is refused with 400 BAD_REQUEST. It failed signature verification, so replaying it would push unverified content to your destinations.
  • An event past your plan's retentionDays is gone. Older events and their deliveries are purged nightly and cannot be replayed.
  • A hard-capped plan at its delivery quota gets 429 QUOTA_EXCEEDED with data: { limit, current, resetAt }. Plans with overage enabled keep accepting and meter the excess. Check GET /v1/billing/usage first if you are about to replay a lot.

Telling a replay from the original

Each replay call is recorded with who ran it, even when it creates no deliveries. Every delivery it creates carries a replayId. On GET /v1/events/{id}/timeline, each delivery branch has a replay object, or null for the original fan-out:

{
  "replay": {
    "id": "rpl_...",
    "at": "2026-09-30T10:12:00.000Z",
    "actorType": "api_key",
    "actorEmail": null
  }
}

actorType is user, api_key or agent. actorEmail is the user's email at the time, and null for keys and agents. There is no endpoint that lists replays on their own. You read them through the timeline or the replayId on a delivery.

curl -s -X POST "$API/v1/events/evt_.../replay" \
  -H "authorization: Bearer $SDHK_KEY"

There is no bulk replay endpoint. To replay many events, list them with GET /v1/events and call replay for each, watching your quota.

Incident playbook

A destination was down for an hour and you want everything it missed.

  1. Confirm the destination is healthy again. POST /v1/destinations/{id}/test-delivery sends a signed sample without creating an event or delivery.
  2. Find the damage. GET /v1/deliveries?status=failed&destinationId=dest_... shows what gave up.
  3. Retry those deliveries one by one. It is free, and each keeps its history. Skip any that return 409, because they are still scheduled.
  4. Replay only if retry cannot work: the delivery is gone, or the event never reached this destination because the connection did not exist yet. Check GET /v1/billing/usage first, since replays are billed.
  5. Do both inside your retention window. After retentionDays, neither is possible.

Replays are new deliveries

A replayed event reaches your receiver under a new delivery id, so a receiver that deduplicates on the delivery id (webhook-id on Standard Webhooks destinations) will process it again. That is usually what you want from a replay. If it is not, deduplicate on something inside the payload instead.

Next steps

  • Retries — the automatic schedule that runs before you step in
  • Deliveries — statuses and attempt history
  • Events — event statuses and what is stored
  • Live streams — watch deliveries land in real time