Webhooks

Retrieve an event

Fetches a single event by id - the same canonical envelope a webhook delivers. A key finds only the events of its own environment, like GET /events: an event of the other environment answers 404.

get/events/{event_id}

Request

  • Base URL
    https://developers.ozav.io/v2
  • Auth
    • API key in header X-API-Key
    • or HTTP bearer

Response

The event.

idstring required

Opaque event identifier, prefixed ozav_evt_.

object'event' required

Resource type discriminator; always the literal event.

type'webhook.test' | 'customer.created' | 'customer.updated' | 'verification.created' | 'verification.updated' | 'screening.completed' | 'screening.monitoring_alert' | 'transfer.updated' | 'transfer.completed' | 'transfer.failed' | 'account.updated' | 'account.credited' | 'deposit.credited' | 'requirement.due' | 'document.reviewed' | 'transfer.approval_required' | 'limit_request.updated' required

Event types currently emitted by API v2 webhooks. The catalog lists only events that actually fire today:

  • webhook.test - queued by POST /webhook-endpoints/{id}/test.

  • customer.created / customer.updated - emitted by the v2 customers surface.

  • verification.created / verification.updated - emitted by the v2 verification surface.

  • screening.completed - emitted when a screening completes.

  • screening.monitoring_alert - a screening under monitoring (POST /screenings/{id}/monitoring) matches a sanctions or PEP entry that was added or changed after it was screened. Sent once per screening and entry.

  • transfer.updated / transfer.completed / transfer.failed - emitted by the v2 transfer surface.

  • card.created / card.updated - card lifecycle only. card.created fires on POST /cards; card.updated on a spending-controls change (PATCH /cards/{id}), on freeze / unfreeze / close, and when the card processor changes the card's status on its side: it confirms a pending card (now active), or freezes, closes (e.g. reported lost) or expires it. A processor change can arrive without any call of yours. Apart from confirming a pending card, it only ever restricts the card, never reopens it.

  • card.fraud_confirmed - emitted when OZAV reviews declines on one of your cards and confirms they were fraud. data is { card, confirmed_at }, with card in the same shape as card.updated. It is a notice, not an action: the card is not frozen or closed by it. Deciding what to do with the card is yours - POST /cards/{card_id}/freeze or /close. It fires at most once per review, is never sent for a decline OZAV judged legitimate, and carries no internal identifiers.

  • card.fraud_case_closed - emitted when OZAV finishes reviewing declines on one of your cards, whatever the outcome. data is { card, outcome, fraud_category, decline_ids, closed_at }, with card in the same shape as card.updated and outcome one of fraud_confirmed, false_positive (the declines were legitimate purchases: the cardholder can try again) or inconclusive. One review covers the declines of one fraud rule on the card: decline_ids lists them with the same ids that GET /cards/{card_id}/declines returns, oldest first, once per purchase even if the card processor retried it. If the review covered more than 500, these are the oldest 500; the rest are on /declines. A decline with no card.declined (for example, the card had no public id yet, or the card processor sent no authorization reference) is not listed, so the list can be empty. fraud_category is the kind of rule, with the same values as on card.declined, and null if that rule no longer exists. A fraud_confirmed review also sends card.fraud_confirmed. It fires at most once per review and carries no internal identifiers.

  • card.authorized - emitted when a purchase on one of your cards is approved and the amount is held. data is { card_id, transaction_id, amount_usd, merchant }. It is sent after the decision has already been returned to the card processor, never inside the processor's authorisation window, so subscribing to it cannot slow down or decline a purchase.

  • card.settled - emitted when a purchase clears. data is { card_id, transaction_id, amount_usd, settled_at, merchant }. amount_usd is the final amount, which can differ from the authorised one (tips, fuel adjustments) - use this one for your ledger, not the authorised figure.

    transaction_id (ozav_ctxn_...) is the id of the same purchase in GET /cards/{card_id}/authorizations and GET /cards/{card_id}/transactions, so the authorisation, its settlement and the row in your ledger reconcile on one key. A settlement of an approved hold carries the same value as its card.authorized, including a settlement that reaches OZAV before the hold was recorded. Only a second capture against the same authorisation (a split shipment) gets its own row and its own id. On card.authorized it is null when the hold row does not exist or could not be read when the event was sent (the authorisation was released first, or recording the hold failed): read /authorizations, or wait for its card.settled.

    card.settled can arrive before card.authorized for the same purchase. When both carry the same transaction_id, upsert on it and never move a settled row back to authorised.

    Both carry the card's public id, never an internal identifier, and are de-duplicated on the processor's own reference: a redelivered authorisation or settlement does not notify you twice. A card that has never been seen through the v2 API has no public id, so no event is sent for it.

  • card.declined - emitted when a purchase on one of your cards is refused. data is { card_id, amount_usd, decline_code, fraud_category, merchant }. A purchase OZAV refuses leaves no hold and no row in /authorizations; it is listed on GET /cards/{card_id}/declines, the same refusal this event announced.

    fraud_category says which kind of fraud rule refused the purchase, and is null for every decline_code other than fraud_rule. It is one of velocity (too many purchases or too much spend in a short window), transaction_amount (one purchase above a limit), period_spend (daily or monthly spend above a limit), merchant_category, merchant_country, time_of_day, or other. The rule's own name and text are never sent.

    decline_code is one of: insufficient_balance, card_frozen, card_inactive, account_inactive, spending_limit, mcc_blocked, country_blocked, presence_blocked, fraud_rule, issuer_declined, or temporarily_unavailable. issuer_declined is a refusal by the card issuer under its own rules; a merchant category the issuer does not allow comes as mcc_blocked.

    When the refusal is on our side rather than the cardholder's, you get temporarily_unavailable instead of our internal reason. It means the purchase can be retried and the cardholder did nothing wrong - which is the only part you can act on. We never send the operator-facing text behind a decision.

  • card.refunded - emitted when a merchant returns money to one of your cards. data is { card_id, transaction_id, amount_usd, refunded_at, merchant }, with transaction_id the refund's own row in GET /cards/{card_id}/transactions.

    amount_usd is positive: the direction travels in the event type, not in the sign, for the same reason /transactions reports direction: credit rather than a negative number. A refund is not an authorisation - the merchant returns and the value credits, with nobody asked - so it never appears in /authorizations.

  • card.reversed - emitted when a held authorisation is released without settling: the merchant cancelled it, it expired, or the card issuer declined it after OZAV approved. The held amount and its surcharge return to the balance. data is { card_id, transaction_id, amount_usd, client_surcharge_usd, reason, reversed_at }, with transaction_id the same as in the card.authorized it closes (null if the hold was never recorded) and reason one of reversed, expired or declined. In the sandbox, trigger it with POST /cards/{card_id}/simulate/reversal.

  • card.three_ds_code - emitted when the card issuer asks for a 3-D Secure one-time code on an online purchase and sends the code to OZAV instead of to the cardholder. Show it to your cardholder so they can finish the purchase. data is { card_id, code, amount, currency, expires_at }, with expires_at an ISO 8601 UTC timestamp; amount, currency and expires_at are null when the issuer does not report them. A code that has already expired is not sent. The code works for that one purchase only: do not log or store it. Webhook redelivery sends the same code again until it expires.

  • card.dispute_created - emitted when you open a dispute with POST /cards/{card_id}/disputes. data is the dispute (CardDispute).

  • card.dispute_updated - emitted each time a dispute changes status (sent_to_issuer, won, lost, withdrawn). data is the dispute (CardDispute). One event per status.

  • card.deletion_scheduled - emitted when a card has been frozen long enough that the card issuer will delete it. data is { card_id, frozen_days, deletes_at }, with deletes_at an ISO 8601 UTC timestamp counted from when the card was frozen (null if that is unknown). Unfreeze the card before then to keep it; a deleted card cannot be restored. You get one notice per freeze and day count, so a card frozen again is warned again.

  • card_float.top_up_blocked - emitted when your automatic card float top-up cannot run because your own master wallet balance is short. Nothing was moved. data is { object: "card_float", reason: "integrator_balance_short", float: { available_amount, currency, floor_amount, target_amount }, top_up: { needed_amount, currency }, integrator_balance: { available_amount, currency } }, all amounts decimal strings in USD (integrator_balance. available_amount is null when it could not be read). Fund your master wallet and the next check tops the float up. You get at most one per day while it stays blocked. See PUT /card-float-settings.

    In the sandbox you create one with POST /cards/{card_id}/simulate/purchase and is_refund: true in the body.

  • account.updated - emitted when a named account's public status changes, so an integrator learns their customer can receive money without polling GET /accounts.

  • account.credited - emitted when money arrives in a customer's BRL named account. data is { account_id, movement }, with movement in the same shape GET /accounts/{account_id}/movements lists it, and the same id. The credit is read from the account's statement every 10 minutes, so the event can arrive up to that long after the payer sent the Pix. It fires once per credit. In the sandbox you create one with POST /accounts/{account_id}/simulate/credit.

  • deposit.credited - emitted when crypto sent to one of your wallets has been credited and is spendable. Receiving crypto needs no call from you: the arrival is observed on chain and credited on its own, and it already shows up in GET /transactions and in GET /wallets/{wallet_id}/balances. This event is the push, so you do not have to poll for it. There is no deposit.pending: before a deposit is credited it can still reorganize on chain, and telling you early would invite you to act on value that may vanish.

  • requirement.due - emitted when compliance review determines which documents are still missing for a customer, so you learn what to collect without polling. Body: {customer_id, object: requirement_request, due_at, reason, reason_label, documents: [{key, label, destination?}], purpose?, missing_fields?}. With reason: destination_unidentified (purpose: travel_rule) each entry of documents carries destination: {chain, address}: say who holds that wallet with POST /customers/{customer_id}/destination-identifications. When the ask is for registration data rather than a file (reason: registration_incomplete or data_inconsistent), missing_fields lists what to send, in the same tokens as requirements.missing_fields (e.g. phone, residential_address, expected_monthly_volume_usd): send them with PATCH /customers/{customer_id} (or, for owners, POST /customers/{customer_id}/associated-persons). Absent when nothing asked for is a field. tax_id / registration there cannot be corrected once the customer is approved or frozen (409 TAX_ID_IMMUTABLE): create a new customer instead. Emitted once per ask, like a document ask. There is no requirement.satisfied: the customer's own GET /customers/{id} shrinks requirements.documents_due when a file lands, and announcing a second event would need a second emitter to avoid becoming an empty promise.

  • document.reviewed - emitted when OZAV's own document review accepts or rejects a supporting document (proof of address, bank statement, proof of income, company document): {customer_id, document_id, key, label, status: accepted|rejected, reason, reason_label, reviewed_at}. A rejection is followed by a fresh requirement.due for the same document, with a new deadline and the reason. Not emitted while a document is waiting on a human reviewer.

  • wallet_policy.updated - emitted when a change to a managed wallet's policy is decided: {wallet_id, change: {id, kind: destination.add|limits.update, status, ...}}. For a destination, status: applied means it is already in destinations.allowed. For limits, status: approved means accepted and not yet in force - the signed record is issued minutes later; confirm with GET /wallets/{wallet_id}/policy (limits.valid_until). rejected carries a reason when one was given. Not emitted when you PATCH: requesting is not changing.

  • transfer.approval_required - not emitted: no send waits on approver keys.

  • limit_request.updated - emitted when the public status of a limit increase request (POST /customers/{customer_id}/limit-requests) changes: awaiting_documents, in_review, approved (with approved_limit) or declined. data is the LimitRequest itself. Once per status. A decline carries no reason.

  • agent_action.updated - emitted when the public status of an agent action (POST /agents/{agent_id}/payments, /payouts, /fundings, /payees, /consumptions, /yield-applications or /accounts, and POST /agent-actions/{action_id}/simulate in the sandbox) changes, to both the agent platform and the company whose money it is. data is the AgentAction itself. Once per status. authorized after awaiting_approval means a person at the company approved: a payout then runs on its own, and for a payment you repeat the request with the same idempotency_key to receive the signature. settled means the token contract shows the authorization used. returned means a payout's transfer ended without delivering the money (cancelled or expired): the mandate's budget is released. A payout becomes settled when its transfer completes. A transfer that needs review (including a Pix return) or that failed stays executed until OZAV reviews it; the event is still sent when the receipt's transfer_status changes. A funding (POST /agents/{agent_id}/fundings) is announced awaiting_payment when its Pix charge exists, then settled when the company paid and the stablecoin arrived, or returned if the charge expired unpaid. A refusal carries the mandate's reasons only; compliance refusals carry no reason. Events are not guaranteed to arrive in order, and a status that changes twice before OZAV reads it (for example authorized then signed in one request) is announced once, as the status it has then: GET /agent-actions/{action_id} is the source of truth. An action waiting for approval that nobody decides ends as expired. When an exception case (dispute) on the action is opened or changes status (open, under_review, resolved_refunded, resolved_denied), the same event goes out with the case in data.exception, once per case status.

  • agent_wallet_withdrawal.updated - emitted to the account holder's organization when a withdrawal from an agent's wallet (POST /agents/{agent_id}/wallet/withdraw, or the one OZAV starts when the agent or its grant is revoked) is created (processing, or empty when a revoked agent's wallet held nothing) and when it ends: settled, refused (settled_amount says what moved before the refusal) or in_review. data is the AgentWalletWithdrawal itself. Once per status; a withdrawal that pauses and carries on later is not announced again until it ends. A status that changes before OZAV reads it is announced once, as the status it has then, and events may arrive out of order. Repeating the request with the same idempotency_key returns the withdrawal as it is now: that answer is the source of truth.

  • vault.activated / vault.failed - emitted when a vault leaves pending (POST /vaults). data is the Vault as GET /vaults/{vault_id} answers it at that moment: with lineage, addresses and the enclave's receipt when active (verify the receipt before you deposit), with failure_code when failed.

  • vault_transaction.signed / vault_transaction.rejected / vault_transaction.expired - emitted when a vault transaction leaves awaiting_approvals. data is the VaultTransaction as GET /vaults/{vault_id}/transactions/{transaction_id} answers it: signed carries signed_transaction (or signature and digest for a typed message), which you broadcast yourself (OZAV never does); rejected carries failure_code; expired means approve_until passed before the threshold closed. The vault events are written in the same database transaction that concludes the vault or the transaction, so an event exists exactly when the state does. Once per status. Events are delivered in the key's environment.

  • vault_root.created - emitted when a root is registered (POST /vault-roots). data is the VaultRoot as GET /vault-roots/{root_id} answers it.

  • vault_grant.created / vault_grant.revoked - emitted when a grant is accepted (POST /vault-roots/{root_id}/grants) and when it is revoked (POST .../grants/{grant_id}/revoke). data is the VaultGrant as the list answers it, GET /vault-roots/{root_id}/grants: how many signatures and how many approvers. It does not carry policy, which only the single read GET /vault-roots/{root_id}/grants/{grant_id} returns. Revocation is announced once, on the stamping.

  • vault_destination_grant.created - emitted when destinations are authorised in a vault (POST /vaults/{vault_id}/destination-grants). data is the VaultDestinationGrant as GET /vaults/{vault_id}/destination-grants/{destination_grant_id} answers it.

  • vault_transaction.requested - emitted when a signature is requested (POST /vaults/{vault_id}/transactions). data is the VaultTransaction at that instant, so approvals.received is 0 and signing is false even when the request already carried approvals[]: the approvals are stored after the transaction row. Read vault_transaction.approval_received, or GET, for the quorum as it stands.

  • The six events above announce steps, and they exist only for facts that happened after they were switched on: a root, grant, destination grant or transaction created before that has no created or requested event, and a grant already revoked then has no vault_grant.revoked. There is no backfill. Reconcile the objects you had before with GET; from then on the events are complete.

  • vault_transaction.approval_received - emitted once per approval stored (POST /vaults/{vault_id}/transactions/{transaction_id}/approvals, or an approvals[] entry of the request). data is the VaultTransactionApproval as GET /vaults/{vault_id}/transactions/{transaction_id}/approvals lists it. A repeated approval from the same approver is announced once: the first one stands.

livemodeboolean

true for an event about live resources, false for one from the sandbox. An endpoint created in the dashboard before it asked for an environment receives both, so branch on this field rather than on how the endpoint was registered. GET /events always returns it. A webhook delivery carries it on every event emitted since the field was introduced; a retry of an older event can arrive without it. When it is missing, look the event up with GET /events/{event_id}: each key finds only the events of its own environment. An endpoint registered with an API key only ever receives events of that key's environment. Before this field was introduced, the customer.updated OZAV sends on its own after a review (a status change, a new limit or a request for information), wallet_policy.updated about a wallet with only a sandbox id, and account.updated about a sandbox account could be recorded as production; such an older event keeps livemode: true.

created_atstring date-time required

When the event occurred (RFC 3339, UTC), not when this delivery attempt was made. It stays the same across retries - use it, not receipt time, to order events.

dataobject required

The affected resource, in the same shape its GET returns. Events about transfers, customers, cards, verifications and screenings carry it under the resource's own name: data.transfer, data.customer, data.card, data.verification, data.screening. Other events document their own data in the event list.

It is a snapshot at event time, and it can be stale by the time you read it. When the current state matters (a decision about money), re-fetch by id rather than acting on this body. Deliveries can also arrive out of order.

Changes

No recorded changes to this operation across all 1 revision of this API.