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.
| Signal | What it proves | What it does not prove |
|---|---|---|
| Cached DAPI mutation response | The mutation request was accepted and returned this response for the idempotency key. | The on-chain transaction is final forever. |
*.final webhook event | The event passed the configured reorg depth. | A later recall cannot supersede it. |
*.retracted webhook event | A reorg invalidated an earlier event. | The original cached DAPI response should remain your source of truth. |
*.recalled webhook event | An 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:
*.finalfor events that passed the configured reorg depth.*.retractedfor events invalidated by a reorg.*.recalledfor 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:
| Field | Source | Use |
|---|---|---|
webhook-id | Delivery header | Dedupe webhook delivery attempts. |
evt_id | Event body | Identify the delivered event envelope. |
is_replay | Event body, when present | Mark replayed envelopes so downstream processing can suppress duplicate actions. |
lifecycle_state | Event body | Decide whether the event is final, retracted, recalled, pending, or provisional. |
request.idempotency_key | Event body, when present | Link the event to the original mutation request. |
supersedes | Event body for retracted and recalled states | Replace 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-idheader. - Reconcile the replayed body against
lifecycle_state,request.idempotency_key, andsupersedesbefore 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
retractedorrecalledevents 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-idheader, then process the event body exactly once for that delivery. - Treat an event with
is_replay: trueas 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
retractedandrecalledevents to supersede earlier accepted or final events. - Treat missing
request.idempotency_keyas an event-only outcome. Do not invent a request correlation key.
Related references
- Read the events catalogue for the event types available to webhook consumers.
- Use token transfer final as a final lifecycle event example.
- Use settlement transfer retracted as a retracted lifecycle event example.
Webhook event integration overview
Subscribe to DALP lifecycle events with AsyncAPI, signed webhooks, idempotent delivery, and typed SDK verification.
access-control.role-granted.provisional
A role was granted on the access-control manager; the grant is provisional until the indexer reaches the configured reorg depth.