Skip to main content
Uses: Python · TypeScript · CLI · REST API
Engini keeps every captured event for 30 days, whether or not it was delivered. So when your receiver was down, rejected signatures, or the destination switched itself off, nothing is lost - you find what went wrong, fix it, and replay. The sequence is check the destination → find the events → read the attempts → fix → re-enable → replay.
cURL samples assume BASE=https://api.engini.io/v1 and AUTH="x-api-key: $ENGINI_API_KEY" - the setup from the REST walkthrough.

How an event ends up undelivered

An event’s delivery is the state of its latest attempt: To tell the two kinds of pending apart, read the event (step 3): an empty attempts list is backlog, and a last attempt with next_attempt_at set is a scheduled retry. Five dead-lettered events in a row auto-disable the destination (max_consecutive_failures, default 5).

1. Check the destination

Start here - if the destination has auto-disabled, nothing is being delivered at all, and last_failure usually names the cause.

2. Find the events that did not arrive

The event log comes back newest first. There is no server-side filter on delivery state, so filter on your side.

3. Read why each attempt failed

GET /v1/triggers/events/{eventId} is the only place with the real attempt history: each attempt’s status code, a trimmed error, how long it took, and when the next one is due.

4. Fix the receiver, then prove the fix

Deploy your fix, then send a test event through the real delivery path before replaying anything. It is signed with the destination’s secret and tells you synchronously what your receiver answered. The test endpoint answers 409 while the destination is disabled, so re-enable it first (step 5) if you need to.
The test endpoint is API-only for now. See Receive trigger events on a webhook for how to read the result.

5. Re-enable the destination

Setting status back to active also resets the failure counter to zero.
Re-enabling resumes most of the backlog on its own. The backstop sweep picks up events from the last 24 hours - and destinations paused mid-retry - within about a minute, with no replay needed. New events are delivered normally from here on. Wait that minute, then re-list (step 2) before deciding what still needs a replay in step 6.

6. Replay what was missed

A replay queues one more attempt, due straight away, and answers 202. It does not deliver inline, so check the event again a few seconds later. The attempt numbering continues from where it left off, so a replay gets whatever retries the event had left - none, if it had already used all six. Only two kinds of event still need a replay after re-enabling: dead_lettered events, and backlog events (pending with no attempts) older than 24 hours. Anything newer already went out on its own - replaying it too is harmless (your receiver dedupes on X-Engini-Event-Id), but there is no reason to.
  • Skip events with a retry already scheduled. Replaying one as well sends it twice - your receiver’s dedupe on X-Engini-Event-Id absorbs that, but there is no reason to.
  • A replay answers 409 (DESTINATION_DISABLED) if the destination is still off.
  • Your receiver sees the same X-Engini-Event-Id it may have seen before, so deduplication protects you if an earlier attempt did get through.

Rotate a secret without losing events

Rotation has no overlap window: the old secret stops verifying the moment the new one is minted. Any delivery that lands in between is refused by your receiver - and because that refusal is a 4xx, the event is dead-lettered rather than retried. So plan to replay.
1

Rotate

Or client.triggers.destinations.rotate_secret("prod") / rotateSecret("prod"). The new esec_… is shown once.
2

Deploy the new secret to your receiver

As fast as your deploy allows - every event that arrives before this finishes will be dead-lettered.
3

Replay the gap

List the trigger’s events, select those that are dead_lettered with an occurred_at after the rotation, and replay them (step 6).
Keep the gap under five events and the destination stays enabled. With a busy trigger, a receiver that answers 503 (not 400) on a signature mismatch during a planned rotation turns those refusals into retries instead of dead-letters - the first retry comes a minute later, by which time the new secret is live.