SettleMint
Events

Idempotency and on-chain outcome

Reconcile cached DAPI mutation responses with webhook-only on-chain outcomes, including final, retracted, and recalled events.

DAPI idempotency protects the synchronous API mutation path. Webhook lifecycle events describe the later on-chain outcome. Keep those two signals separate. A cached DAPI mutation response reflects the synchronous mutation outcome only: the platform accepted the mutation and returned the same response for the same idempotency key. It cannot prove that the originating transaction stayed final on-chain. Use webhooks to observe that on-chain status.

Outcome model

Treat each signal according to the state it can prove.

SignalWhat it provesWhat it does not prove
Cached DAPI mutation responseThe mutation request was accepted and returned this response for the idempotency key.The on-chain transaction is final forever.
*.final webhook eventThe event passed the configured reorg depth.A later recall cannot supersede it.
*.retracted webhook eventA reorg invalidated an earlier event.The original cached DAPI response should remain your source of truth.
*.recalled webhook eventAn operator-authorized compliance recall superseded an earlier event.That the earlier event reached your consumer before the recall became authoritative. Unknown supersedes references still need audit logging.

This rule is the boundary between request idempotency and chain reconciliation. Treat an HTTP 201 returned from an idempotency cache as "the mutation was accepted and returned this response". Do not treat it as "the transaction is final forever".

Reconciliation pattern

Subscribe to the lifecycle events that can prove or revise an outcome:

  • *.final for events that passed the configured reorg depth.
  • *.retracted for events invalidated by a reorg.
  • *.recalled for operator-authorized compliance recall.

Dedupe every delivery by the webhook-id header. Correlate webhook events back to the originating API call with event.request.idempotency_key when that value is present. If a later retracted or recalled event names an earlier event through supersedes, treat the later event as authoritative over the cached DAPI response.

Webhook replay uses the same consumer rules. DALP can replay one event by evt_id, optionally guarded by chain ID, or replay a block range for a specific chain. A replay queues a fresh delivery envelope with its own evt_id and is_replay: true; it does not create a new on-chain outcome, change the original lifecycle state, or replace signature verification and delivery deduplication in your consumer.

A safe consumer stores at least these fields for reconciliation:

FieldSourceUse
webhook-idDelivery headerDedupe webhook delivery attempts.
evt_idEvent bodyIdentify the delivered event envelope.
is_replayEvent body, when presentMark replayed envelopes so downstream processing can suppress duplicate actions.
lifecycle_stateEvent bodyDecide whether the event is final, retracted, recalled, pending, or provisional.
request.idempotency_keyEvent body, when presentLink the event to the original mutation request.
supersedesEvent body for retracted and recalled statesReplace the earlier event with the later authoritative outcome.

Retract and recall handling

When a *.retracted event arrives, mark the superseded event as no longer valid and reverse any external side effects that assumed finality. When a *.recalled event arrives, apply the recall even if the original delivery failed or is unknown to your system. Unknown supersedes references should be ignored after audit logging.

If both retraction and recall apply to the same original event, recall wins. Your consumer should keep the recall as the final superseding signal. Keep this retract and recall handling branch close to your idempotency correlation code. Cached DAPI responses must not mask later chain outcomes.

Replay handling

Use replay as a recovery tool, not as a shortcut around idempotent processing. DALP exposes webhook replay for a specific event ID or for a block range on a specific chain. A block-range replay needs the chain ID so the replay can be scoped to the right indexed chain.

Replay can deliver an event that your consumer has already seen. Keep the same safeguards in place during replay as during live delivery:

  • Verify the webhook signature before processing the body.
  • Dedupe each delivery attempt with the webhook-id header.
  • Reconcile the replayed body against lifecycle_state, request.idempotency_key, and supersedes before updating downstream state.
  • Store the stable domain identifiers from the event payload, such as chain, transaction, log, and resource identifiers documented on the event page. Use those identifiers to recognise an outcome that was already processed before replay.
  • Treat replayed retracted or recalled events as authoritative over the cached mutation response when they supersede an earlier outcome.

That pattern lets an operations team recover missed deliveries without double-applying a mint, transfer, settlement, or recall in the receiving system. The delivery envelope tells you whether you have seen that delivery attempt. The event payload tells you whether you have already processed the underlying on-chain outcome.

Typed SDK supersedes accessor

The typed SDK exposes supersedes only on retracted and recalled lifecycle states. That lets TypeScript consumers branch on lifecycle state before applying authoritative-over-cached-response behavior.

import { verifyWebhook, type Webhook } from "@settlemint/dalp-sdk";

const result = verifyWebhook({ rawBody, headers, secret });
if (!result.ok) throw new Error(result.code);

const event: Webhook.Event = result.event;

if (event.lifecycle_state === "retracted" || event.lifecycle_state === "recalled") {
  await markSuperseded({
    originalEventId: event.supersedes,
    supersedingEventId: event.evt_id,
    idempotencyKey: event.request?.idempotency_key,
  });
}

verifyWebhook returns the discriminated event union after signature verification, so reconciliation code can narrow on event.type or event.lifecycle_state.

Consumer safeguards

Keep the reconciliation code conservative:

  • Apply webhook signature verification before reading the event body.
  • Dedupe delivery attempts with the webhook-id header, then process the event body exactly once for that delivery.
  • Treat an event with is_replay: true as a replayed envelope for an existing outcome, not as permission to apply the business action again.
  • Store cached mutation responses separately from event outcome state.
  • Allow later retracted and recalled events to supersede earlier accepted or final events.
  • Treat missing request.idempotency_key as an event-only outcome. Do not invent a request correlation key.

On this page