Skip to main content
An active endpoint receives every event listed here for its business. Event filtering is not available, so route on event and ignore what you do not handle.

Event catalog

Two events are reserved: fiat.deposit.created and fiat.deposit.failed. They are part of the catalog but are not currently emitted — current provider flows expose no durable local state to emit them from safely. Build handlers for them; do not wait on them.fiat.deposit.completed is emitted: it fires for the credit side of an internal transfer, so money arriving on one of your accounts from another Sakawave account does reach you.
The customer KYC events follow a customer’s verification for as long as it exists, not only its first verdict. Each is sent whenever its status is reached: customer.kyc.suspended when an approval is withdrawn, and customer.kyc.approved or customer.kyc.rejected again if a later review changes the outcome. Their data is the customer object plus a kyc object with status, tier, failureReason and resolvedAt. See Identity and verification. There are no transfer.* events. An outbound transfer reaches your endpoint as a fiat.withdrawal.* event whose relatedOperation describes the transfer.

Envelope

Every request body has exactly two top-level fields.
The event identifier, timestamp, and attempt number travel as headers, not as body fields. Deduplicate on the Sakawave-Webhook-Id header — there is no id inside the body.

Financial payloads

Deposits, withdrawals, bill payments, and card transactions carry the posted ledger transaction. amount and fee are strings of minor units; their Formatted counterparts, and converted amounts inside relatedOperation, are JSON numbers in major units.
status is derived from the event name, so fiat.withdrawal.failed always carries status: "failed". relatedOperation.type is transfer, bill_payment, or card_payment.
  • transfer — reference, destination type, recipient display name, institution name, last four digits, source and destination amounts and currencies, and nullable failure fields. Inbound movements add direction: "inward", sessionId, and senderName. Bank resolution is best effort: institutionName is null when the NIP bank code cannot be resolved.
  • bill_payment — biller and package names, customer name and address, the last four characters of the customer reference, vend status, token data where the biller returns one, and a nullable failure reason.
  • card_payment — card ID, card label, masked PAN, merchant name, and merchant country.

Resource payloads

account.*, account.detail.*, card.*, and customer.* events carry the same sanitized object their REST endpoint returns — byte for byte what a GET would give you. Provider identifiers, regulated identity values, full card numbers, CVVs, and ledger balances are never added to a webhook payload. Because the payload is the resource, a resource event can be acted on directly without a follow-up GET — unlike a financial event, where the money detail sits in relatedOperation and the authoritative object is elsewhere.

eSIM payloads

esim.* events follow the resource rule, with two shapes rather than one. An order carries both money figures: price in USD, and paid in whatever currency the account was actually debited. Reconcile a bank statement against paid; reconcile the catalog against price.
profileIds is what makes esim.order.completed actionable: fetch the install details for each and put the QR in front of the customer.
Activation codes are never in a webhook payload. They install a working SIM, so they are only ever handed out by an authenticated request for one specific eSIM.
esim.usage.low and esim.expiry.near fire once per eSIM, not on every usage sync. You can send them straight to a customer without keeping your own “already warned” flag.

Deduplication is per revision

Resource events are staged with an idempotency key built from the event type, the resource id, and a revision taken from the object’s updatedAt. Two updates that produce the same updatedAt are collapsed into one event. A .created event uses a fixed revision, so a resource is only ever announced once.

Ordering and duplicates

Do not assume global ordering. A manual resend can deliver an older event after a newer one. Apply a change only when it is valid for your current state, and treat the REST resource as the source of truth when an event conflicts with it. Deduplication must be atomic with recording receipt. A typical table uses the webhook ID as a unique primary key and stores the event name, received time, processing status, and a body hash. Return 2xx for an event you have already processed.