Webhook Events
This page covers which events CrissCross sends and what each one contains. For subscribing an endpoint, verifying signatures, retries, and replay, see Webhooks.
Event Types
CrissCross sends webhook events for transactions (payments in), refunds, payouts (payments out), payout beneficiaries, and rate locks.
Transaction event types:
Refund event types:
Payout event types:
Payout beneficiary event types:
Rate lock event types:
Rate lock events are the exception on this page: they carry a rate-lock payload wrapped in an envelope, { eventType, timestamp, payload }, so for those two events the event type is readable from the body. See Rate Locks and the Rate Lock Webhooks API reference for the schema. Every other event on this page uses one of the bodies below, which carry no envelope.
Body Types
Every event above except the rate lock ones sends one of four bodies, delivered as the HTTP body with no envelope around it. Which one you receive depends on the event:
Transaction body
The transaction the event is about. For a refund.* event this is the refund itself; for transaction.refunded it is the original payment.
Nullable fields arrive as an explicit null when unset. Optional fields are left out of the body entirely. Parse accordingly.
FX payments carry collection, fxMidRate, fxAllInRate, fxMarkupBps, and fxFee as well. See Adaptive Currency Conversion.
authState
authState describes the current state in more detail. What it carries depends on the status — when it is present, state and transitionedAt always are:
Things to watch:
REFUNDEDbodies have noauthStateat all. A handler that assumes the key exists will break ontransaction.refunded.ERROREDreportsauthState.stateaserror, noterrored. Match on the value, not on the event name.codeuses different values forFAILEDthan forERRORED— authentication failure codes versus error codes. See Error Codes.- The top-level
messageis not a failure reason. OnFAILED,ERRORED,CANCELLED,EXPIRED,SETTLED, andREFUNDEDit is always the string"Transaction queried successfully". Take the reason fromauthState.messageandauthState.code. SETTLEDcarries no settlement identifier. Use the settlement endpoints to find which settlement a transaction landed in.
Late payment summary
Sent when a payment arrives after its order was already cancelled or expired. It is a summary, not the full transaction — call Retrieve Payment Status if you need the rest.
Release summary
Sent when a held transaction is released and treated as completed.
Beneficiary
The payout beneficiary the event is about. status always matches the event.
Account-name verification runs for mobile money only. A bank account is screened but not name-checked, so its validation stays at verified: false, state: "PENDING", attempts: 0 even once the beneficiary is validated — read status, not validation.verified, to decide whether it can be paid out to. For the same reason payout_beneficiary.partial_match only ever fires for mobile money.
Example Webhook Payloads
Every delivery arrives with the same headers, and none of them names the event. For the bodies below the event type is not in the body either, so route by subscribing each endpoint to the specific events you want. (Rate lock events are the exception — see above.)
The bodies below are complete, exactly as delivered.
A completed transaction (transaction.completed):
A completed payout (payout.completed):
A completed refund (refund.completed). This is the refund’s own transaction, not the original payment:
A refund copies the original payment’s merchantReference, identifiers, paymentMethodId, and paymentAttributes, but it is its own transaction: transactionId, message, processorReference, currentAttemptId, and authState are the refund’s, not the payment’s. There is no original-transaction id or transaction type in the body. So:
- Match a refund to its payment from your own records: when you create the refund, persist the returned
transactionIdtogether with the response’soriginalTransactionId, and look the webhook’stransactionIdup against that.merchantReferenceandidentifiers.sessionIdare not reliable keys — a session can hold more than one refundable transaction. - Tell the two records apart by
transactionId— keying onmerchantReferencealone risks double-counting. - Give
refund.*its own endpoint. Nothing in the body separates it fromtransaction.*.
A late payment that arrived against a cancelled order and was automatically refunded (transaction.auto_refunded). transaction.held_pending_review and transaction.auto_refund_failed carry the same two fields:
A held transaction that was released (transaction.released) — the same shape without reason:
A mobile money beneficiary that passed validation and screening (payout_beneficiary.validated). All four payout_beneficiary.* events carry this same object, with status matching the event:
status, type, and amlScreening.state are prefixed enum names rather than bare words, so compare against the full string — see Beneficiary for the values. The error and reason fields are omitted when empty, so expect them on partial_match, screening_review, and rejected rather than on validated.