Skip to main content
A Canton integration is a webhook consumer first. An inbound offer is a status update on a transaction you never created, so there is nothing to poll for until the webhook fires. Fireblocks delivers these to the endpoint you register. You implement the receiver. The envelope is the standard Fireblocks webhook envelope, not a Canton-specific one, and the data object is the standard transaction notification. The only Canton-specific thing on the wire is the cantonDetails key on data.

Which event fires

Only TRANSACTION_STATUS_UPDATED carries cantonDetails. A Canton integration can discard every other event type at the front door.

Route an incoming notification

To answer in step 6, call Answer an offer with data.id as offerId. Step 4 covers calls you submitted with Make a Canton call. Both are covered end to end in Write Canton Transactions.
Step 6 is the answerability rule. Branch on availableResponses, never on subStatus. Examples 1 and 4 below share status: CONFIRMING and subStatus: PENDING_RECEIVER_APPROVAL, and only one of them is answerable.

The five states to tell apart

An inbound DTCC end-investor onboarding offer. availableResponses carries both DTCC values, so this transaction ID is the one to answer.

Sub-status reference

Note that CONFIRMING is not transient for Canton. An accepted allocation sits at CONFIRMING with ALLOCATED until the venue settles it.

Delivery semantics

Delivery is at-least-once and unordered. Two notifications for the same transaction can arrive out of timestamp order, and timestamp is not a sequence number. Treat every notification as a state snapshot rather than a transition. There is no previousStatus field, by design. Re-read availableResponses on each notification instead of tracking transitions yourself, and make handling idempotent on data.id plus data.status plus data.subStatus. Parse permissively. Both the envelope and data carry fields beyond this contract, and a strict parser breaks on the next unrelated platform addition.

What your endpoint should return

Acknowledge first and process asynchronously. Holding the connection open while a handler runs risks a timeout, which is indistinguishable from a failure and triggers a redelivery.
Return 5XX for a transient failure, because redelivery is what you want. Do not return 4XX for a payload you will never accept, such as one with no cantonDetails, because that becomes a retry loop. Return 200 and drop it instead.

Reconciling without webhooks

The webhook is the push half of the read side. GET /v1/transactions/{txId} is the pull half, and both carry the identical top-level cantonDetails object. Use the pull path at startup, after downtime, or when a delivery fails. See Read Canton Transactions.