Webhook retries
Rundun retries failed webhook deliveries automatically using a fixed backoff schedule delivered via Rundun's delivery infrastructure.
Retry schedule
| Attempt | When |
|---|---|
| 1 | Immediate — within seconds of the event firing |
| 2 | 30 seconds after attempt 1 |
| 3 | 2 minutes after attempt 2 |
| 4 | 10 minutes after attempt 3 |
| 5 | 1 hour after attempt 4 |
After attempt 5, the event is permanently marked failed. No further retries occur. The total window from first attempt to final failure is approximately 1 hour and 12.5 minutes.
What counts as a failure
A delivery attempt fails when:
- Your endpoint returns any non-2xx HTTP status code (including 3xx redirects, 4xx, 5xx)
- Your endpoint does not respond within 10 seconds
- The connection cannot be established (DNS failure, connection refused, etc.)
Rundun does not inspect the response body — only the status code matters.
Idempotency
Each event has a stable event_id UUID. This value is identical across all retry attempts for the same event. Use it as an idempotency key to avoid processing the same event twice:
// Store processed event_ids in your database
app.post('/hooks/rundun', async (req, res) => {
// Acknowledge immediately
res.status(200).json({ received: true })
const { event_id, event, run_id, data } = req.body
// Deduplicate using event_id
const alreadyProcessed = await db.webhookEvents.findOne({ event_id })
if (alreadyProcessed) return
// Mark as processed before doing the work (prevents double-processing on crash)
await db.webhookEvents.insertOne({ event_id, processed_at: new Date() })
// Now do the actual work
await handleEvent({ event, run_id, data })
})
The delivery_attempt field (1–5) tells you which attempt number this delivery is. It is useful for logging and debugging but you should not use it for idempotency — use event_id instead.
What to do when an event fails permanently
If a webhook event reaches 5 failed attempts, you have two options:
-
Pull the run state manually — use
GET /v1/runs/:run_idto retrieve the current run state including all answers. This is always available regardless of webhook delivery status. -
Check delivery history in the dashboard — go to my.rundun.app → Runs → [run] → Webhook deliveries to see each delivery attempt, the HTTP status returned, and the response body.
Handling retries gracefully
Respond fast. Your endpoint has 10 seconds. Return 2xx immediately and process the event asynchronously:
app.post('/hooks/rundun', async (req, res) => {
// Respond before doing any work
res.status(200).json({ received: true })
// Enqueue for async processing — do not await here
await queue.enqueue(req.body).catch(console.error)
})
Do not return 4xx to suppress retries. If your endpoint returns 4xx, Rundun will still retry. To suppress retries (e.g. if you are intentionally ignoring certain events), return 200 — that is the only way to stop delivery attempts.
Handle timeouts gracefully. If your endpoint processes synchronously and occasionally takes over 10 seconds, you will receive duplicate deliveries for the same event. Idempotency via event_id protects against this.
Monitoring
Check the delivery status and per-attempt response details in the creator dashboard under Runs → [run] → Webhook deliveries.
You can also retrieve run state at any time via GET /v1/runs/:run_id as a fallback regardless of webhook delivery status.