Idempotency and on-chain outcome
Reconcile cached DAPI mutation responses with webhook-only on-chain outcomes, including final, retracted, and recalled events.
A cached DAPI mutation response reflects the synchronous mutation outcome only; it does not track on-chain fate. The originating transaction can still be dropped from the mempool, reorged out, or recalled for compliance reasons. Use webhooks to observe that on-chain status.
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", not "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.
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 so cached DAPI responses cannot mask later chain outcomes.
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.