---
title: "Event catalogue"
description: "Every webhook event Gravv sends, when it is sent, its fields and an example."
---
This page lists every event Gravv sends to your webhook URL, grouped by what it is about. Each section gives the event types, when each one is sent, the fields in `event_data` and an example. The fields every event shares are on [Webhook payload structure](/platform/webhooks/webhooks-overview#webhook-payload-structure).

If an event type is not on this page, Gravv does not send it.

**Example.** Ada pays a supplier 100.50 USD. You receive `transfer.status.pending` three times, one for each step, with `idempotency_key` ending in `:1`, `:2` and `:3`. Then `transfer.status.completed` arrives with `:4`, and you mark the invoice paid. If the `:3` event reaches you after the `:4` one, you ignore it, because its lower number means it is older.

## How to read an event

- `event_type` is the full type, such as `transfer.status.completed`. `event_category` is the first part, such as `transfer`. [Webhook history](/platform/webhooks/webhooks-overview#find-and-resend-missed-events) filters by it.
- `event_group_id` is the same for every event about one thing: one transfer, deposit, card or customer. Each section says which id it is.
- Deduplicate on the body's `event_id`. It stays the same when an event is retried or resent.
- Keys in `event_data` arrive in alphabetical order. Gravv can add new keys at any time, so ignore the ones you do not know.
- Amounts in `event_data` are strings, such as `"100.5"` or `"1500.00"`, unless a section says otherwise.
- Gravv compares events that carry no `idempotency_key` by their content. It does not send an event identical to an earlier one for the same object. When a card goes `active`, then `freeze`, then `active` again, Gravv does not send the second `active`. Read the object after a change you made yourself.
- Events can arrive in any order. Each section says how to tell which is newer.

## All event types

| Event type | Category | Sent when |
| :-- | :-- | :-- |
| `transfer.status.pending` | `transfer` | A transfer moved to a new step. Several arrive for one transfer |
| `transfer.status.completed` | `transfer` | The transfer was paid |
| `transfer.status.failed` | `transfer` | The transfer failed, or a person must check it |
| `transfer.status.returned` | `transfer` | A completed payout came back and the money was returned |
| `transfer.requires_information` | `transfer` | The transfer paused and waits for you |
| `transfer.resumed` | `transfer` | A paused transfer resumed |
| `transfer.inflow.deposit_received` | `transfer` | A deposit in another currency is being converted |
| `transfer.inflow.awaiting_approval` | `transfer` | A deposit is held for approval |
| `transfer.inflow.completed` | `transfer` | A deposit was credited to the account |
| `transfer.inflow.deposit_settled` | `transfer` | A deposit was credited and settled on-chain |
| `transfer.inflow.payout_failed` | `transfer` | Crediting a deposit failed |
| `transfer.inflow.compensated` | `transfer` | A failed deposit was reversed |
| `inflow.onchain.completed` | `inflow` | Stablecoins arrived at a wallet address |
| `inflow.onchain.failed` | `inflow` | An incoming on-chain transfer failed on chain |
| `account.activated` | `account` | A bank account Gravv registered for you is ready |
| `applications.accounts.pending` | `applications.accounts` | A v2 account application was accepted |
| `applications.accounts.blocked` | `applications.accounts` | A v2 account application waits on you or the customer |
| `applications.accounts.completed` | `applications.accounts` | The v2 account is active |
| `applications.accounts.failed` | `applications.accounts` | The v2 account application ended without an account |
| `external_account.created` | `external_account` | An external account was added |
| `external_account.active` | `external_account` | An external account's setup succeeded |
| `external_account.setup_failed` | `external_account` | An external account's setup did not succeed |
| `cards.application.update` | `cards` | A card application changed status |
| `cards.status.update` | `cards` | A card changed status |
| `cards.transaction.payment` | `cards` | A card payment was recorded or changed status |
| `cards.transaction.funding` | `cards` | Money was added to a card |
| `cards.transaction.withdraw` | `cards` | Money was taken off a card |
| `cards.dispute.submitted` | `cards` | A dispute on a card payment was filed |
| `cards.dispute.updated` | `cards` | A dispute is under review |
| `cards.dispute.resolved` | `cards` | A dispute ended |
| `collection.status.pending` | `collection` | A collection is in progress, expired or was cancelled |
| `collection.status.awaiting_capture` | `collection` | A card payment is authorized and waits for capture |
| `collection.status.provider_success` | `collection` | The payment succeeded and settlement has not finished |
| `collection.status.completed` | `collection` | The money reached your destination |
| `collection.status.failed` | `collection` | The payment failed, was abandoned, or a refund failed |
| `collection.status.provider_voided` | `collection` | A card authorization was voided |
| `collection.status.refund_pending` | `collection` | A refund started |
| `collection.status.refunded` | `collection` | The payment was refunded |
| `collection.status.disputed` | `collection` | The payer opened a chargeback, or one was lost |
| `customer.created` | `customer` | A customer or an associated person was added |
| `customer.kyc.status.pending` | `customer` | Verification started or is still under way |
| `customer.kyc.status.completed` | `customer` | Verification passed |
| `customer.kyc.status.failed` | `customer` | Verification did not pass |
| `feature_access.requested` | `feature_access` | You asked for access to a feature |
| `feature_access.action_required` | `feature_access` | Gravv needs more information about your request |
| `feature_access.approved` | `feature_access` | Your request was approved |
| `feature_access.rejected` | `feature_access` | Your request was rejected |
| `feature_access.suspended` | `feature_access` | Gravv suspended a feature for your business |

## Transfers

Gravv sends these events for every [transfer](/platform/transfers/overview): payouts, transfers between accounts and wallets, card funding and withdrawal, and conversions. The category is `transfer`.

- `event_group_id` is the transfer's `reference`.
- `idempotency_key` is `<reference>:<sequence>`. The sequence goes up by one with each change. Apply an event only when its sequence is higher than the last one you applied for that transfer.
- The top-level `transaction_id` is the first id in `associated_transactions`.

| Event type | `status` | Sent when |
| :-- | :-- | :-- |
| `transfer.status.pending` | `pending` | The transfer moved to a new internal step. Gravv sends several for one transfer. Treat them all as "still moving" |
| `transfer.status.completed` | `completed` | The transfer was paid |
| `transfer.status.failed` | `failed` | The transfer failed or was rejected, and Gravv returned the money and fee to the source. Gravv also sends it when `transfer_status` is `manual_processing_required`. In that case a person is checking the transfer, it is **not** final, and you must not send it again. Gravv also sends it, with `reason`, when a pause expired or was cancelled |
| `transfer.status.returned` | `returned` | Sent only after `completed`. The payout came back, and Gravv returned the money to the source |
| `transfer.requires_information` | `paused` | The transfer paused for a limit, a document or the sender's details. See [Paused transfers](/platform/transfers/paused-transfers) |
| `transfer.resumed` | `pending` | A pause ended because you resumed it or Gravv released it |

| Field | Type | Description |
| :-- | :-- | :-- |
| `amount` | string | The transfer amount, without trailing zeros, such as `"100.5"`. There is no currency field, so read the transaction |
| `associated_transactions` | array of strings | Your [transaction](/api-reference/transactions/get-v1-transactions-transaction-id) ids for this transfer. Left out when there are none |
| `client_reference` | string | Your `client_reference` from the request. Left out when you sent none |
| `destination_id` | string | The `destination.id` from the request. Left out when there was none |
| `reference` | string | The transfer's `reference`, the id you resume a paused transfer with |
| `status` | string | `pending`, `completed`, `failed`, `paused` or `returned` |
| `transfer_status` | string | Gravv's internal step. Its values can change, so build on `status`. Read it only on `transfer.status.failed`, to tell `manual_processing_required` from a final failure |
| `reason` | string | On `transfer.requires_information`, why it paused. On `transfer.resumed`, `resumed` or `released`. On a pause that ended without resuming: `limit_pause_expired`, `limit_pause_cancelled`, `supporting_document_expired`, `supporting_document_cancelled`, `sender_details_expired` or `sender_details_cancelled` |
| `limit_details` | object | Only on a pause: `reason`, `breached_window`, `cap_usd`, `used_usd`, `remaining_usd`, `amount_usd`, `fee_usd`, `tier`, `subject_type`, `subject_id`, `policy_id`, `policy_version`, and `requirements` (a list of `type`, `tier`, `levels`, `description`) |
| `tx_hash` | string or null | The on-chain hash, when the transfer has one |
| `workflow_type` | string | The kind of transfer: `crypto_transfer`, `internal_transfer`, `auto_sweep`, `card_funding`, `card_withdrawal`, `global_remittance`, `us_bank_transfer`, `sepa_transfer`, `fps_transfer`, `swift_transfer`, `chaps_transfer`, `cross_chain_self_transfer` or `stablecoin_conversion` |
| `send_asset`, `amount_sent`, `receive_asset`, `receive_amount`, `min_receive_amount`, `provider_fee`, `refunded_amount`, `refund_asset` | string | Only on a stablecoin conversion: what was sent, what was or will be received, and the conversion fee. When a conversion failed and was refunded, `receive_amount` is left out and `refunded_amount` and `refund_asset` are set |

```json title="transfer.status.completed"
{
  "tenant_id": "dfa96ede-fc13-43cf-8328-8b910d1cd1d2",
  "transaction_id": "5d1e7a3b-2c4f-4b8e-9d6a-1f0e3c2b7a95",
  "event_category": "transfer",
  "event_type": "transfer.status.completed",
  "event_group_id": "9a7c1e52-6b3d-4f0a-8e21-7d5c4b3a2f10",
  "timestamp": "2026-10-13T09:15:02.481263Z",
  "event_data": {
    "amount": "100.5",
    "associated_transactions": ["5d1e7a3b-2c4f-4b8e-9d6a-1f0e3c2b7a95"],
    "client_reference": "inv-2291",
    "destination_id": "c2f5a0b1-8d3e-4a6f-9b7c-0e1d2f3a4b5c",
    "reference": "9a7c1e52-6b3d-4f0a-8e21-7d5c4b3a2f10",
    "status": "completed",
    "transfer_status": "completed",
    "workflow_type": "global_remittance"
  },
  "event_id": "e4b1c0d2-3f5a-4e6b-8c7d-9a0b1c2d3e4f",
  "idempotency_key": "9a7c1e52-6b3d-4f0a-8e21-7d5c4b3a2f10:4"
}
```

## Deposits into accounts

Gravv sends these events when money arrives in one of your customers' [accounts](/platform/accounts/receive-deposits). The category is `transfer`, so webhook history returns them with the transfer events.

- `event_group_id` is the deposit's `transaction_id`. On `transfer.inflow.payout_failed` it is Gravv's own id for the deposit.
- Gravv sends each type at most once for one deposit.
- Gravv credits the deposit in full and charges any deposit fee separately. See [Add up a month of fees](/recipes/transactions/add-up-a-month-of-fees).

| Event type | `status` | Sent when | Fields besides the sender fields |
| :-- | :-- | :-- | :-- |
| `transfer.inflow.deposit_received` | `deposit_received` | A deposit in another currency is being converted to the account's currency. Not sent when the currencies match | `transaction_id`, `account_id`, `amount` (the estimated amount after conversion), `currency` (the account's), `reference`, `remark`, `tx_hash` (the conversion's reference, not a chain hash), `source` and `destination` |
| `transfer.inflow.awaiting_approval` | `awaiting_approval` | The deposit is held for approval before it is credited | `transaction_id`, `account_id`, `amount`, `currency` |
| `transfer.inflow.completed` | `completed` | The deposit was credited | `transaction_id`, `account_id`, `amount`, `currency`, and `settlement_event_group_id` when it was also settled on-chain |
| `transfer.inflow.deposit_settled` | `settled` | The deposit was credited and settled on-chain to the account's wallet | The same as `completed`, plus `tokenized` `true` |
| `transfer.inflow.payout_failed` | `failed` | Crediting the deposit failed | `account_id`, `error`, `failed_at`, and `transaction_id` `""` |
| `transfer.inflow.compensated` | `compensated` | A failed deposit was reversed | `account_id`, `deposit_id`, `failure_reason` |

Every type also carries the sender fields `sender_name`, `sender_account_number`, `sender_bank`, `session_id` and `narration`. They are strings and can be empty.

For a credited deposit, Gravv sends `transfer.inflow.completed` or `transfer.inflow.deposit_settled`, depending on the account. Treat either one as credited. When the deposit was also settled on-chain, Gravv sends a second event, `inflow.onchain.completed`, for the same money. Its `event_group_id` equals `settlement_event_group_id` here. Credit your customer once.

```json title="transfer.inflow.completed"
{
  "tenant_id": "dfa96ede-fc13-43cf-8328-8b910d1cd1d2",
  "transaction_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
  "event_category": "transfer",
  "event_type": "transfer.inflow.completed",
  "event_group_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
  "timestamp": "2026-10-13T08:02:11Z",
  "event_data": {
    "account_id": "4f3e2d1c-0b9a-4876-9543-210fedcba987",
    "amount": "2500",
    "currency": "USD",
    "narration": "Invoice 4471",
    "sender_account_number": "0123456789",
    "sender_bank": "Example Bank",
    "sender_name": "Jane Doe",
    "session_id": "",
    "status": "completed",
    "transaction_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"
  },
  "event_id": "c0ffee00-1234-4abc-9def-001122334455",
  "idempotency_key": "6c1f0a9e2b7d4c3a8e5f1b0d9c7a6e4f2d3b1a0c9e8f7d6c5b4a3f2e1d0c9b8a"
}
```

## On-chain deposits

Gravv sends these events when USDC or USDT arrives at a [wallet](/platform/wallets/overview) address, or at the wallet address of an account. The category is `inflow`.

| Event type | Sent when |
| :-- | :-- |
| `inflow.onchain.completed` | An incoming on-chain transfer was detected. Sent once |
| `inflow.onchain.failed` | An incoming on-chain transfer failed on chain |

- Gravv derives `event_group_id` from the transaction hash, so both events for one deposit share it (see [Deposits into accounts](#deposits-into-accounts)).
- There is no `idempotency_key`.

| Field | Type | Description |
| :-- | :-- | :-- |
| `amount` | string | The amount received |
| `asset` | string | The token received. It can be the token's contract address, so read `asset_symbol` when it is present |
| `asset_symbol` | string | The token's symbol, such as `USDC`. Left out when unknown |
| `balance_before`, `balance_after` | string or null | The wallet's balance before and after, when known |
| `blockchain_network` | string | The network, such as `polygon` |
| `confirmations` | number | Confirmations at detection |
| `destination` | object | `wallet_address`, `blockchain_network`, `blockchain_memo` (always `null`), and the wallet or account `id` and `destination_type` when known |
| `source` | object | The sender's `wallet_address` and `blockchain_network`, and `blockchain_memo` (always `null`) |
| `remark` | string | `Crypto Funding`, or `inbound transaction failed on chain` |
| `status` | string | `COMPLETED` or `FAILED`, in upper case |
| `transaction_id` | string | The chain transaction as `<network>:<hash>`. It is not the id of a transaction row. Find the row by `tx_hash` |
| `tx_hash` | string | The transaction hash |

```json title="inflow.onchain.completed"
{
  "tenant_id": "dfa96ede-fc13-43cf-8328-8b910d1cd1d2",
  "transaction_id": "polygon:0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060",
  "event_category": "inflow",
  "event_type": "inflow.onchain.completed",
  "event_group_id": "3b8f1d2e-4c5a-5b6d-8e7f-9a0b1c2d3e4f",
  "timestamp": "2026-10-13T07:58:40Z",
  "event_data": {
    "amount": "250.00",
    "asset": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
    "asset_symbol": "USDC",
    "balance_after": "1250.00",
    "balance_before": "1000.00",
    "blockchain_network": "polygon",
    "confirmations": 12,
    "destination": {
      "blockchain_memo": null,
      "blockchain_network": "polygon",
      "destination_type": "wallet",
      "id": "0d9c8b7a-6f5e-4d3c-2b1a-0f9e8d7c6b5a",
      "wallet_address": "0x8ba1f109551bD432803012645Ac136ddd64DBA72"
    },
    "remark": "Crypto Funding",
    "source": {
      "blockchain_memo": null,
      "blockchain_network": "polygon",
      "wallet_address": "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984"
    },
    "status": "COMPLETED",
    "transaction_id": "polygon:0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060",
    "tx_hash": "0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060"
  },
  "event_id": "aa11bb22-cc33-4dd4-8ee5-ff6600778899"
}
```

Gravv also sends events for other tokens that land on an address, such as a spam token. Credit only USDC and USDT.

## Account applications

Gravv sends these events as a v2 account you open with [`POST /v2/accounts`](/platform/accounts/open-an-account) moves from creation to `active`. The category is `applications.accounts`, and `event_group_id` is the application id.

| Event type | Sent when |
| :-- | :-- |
| `applications.accounts.pending` | The application was accepted. Gravv can skip this event, so do not depend on it |
| `applications.accounts.blocked` | The application waits on you or the customer: `kyc_pending`, `tos_pending`, `awaiting_customer_document` or `awaiting_customer_data` |
| `applications.accounts.completed` | The account is `active`. It carries no bank details, so read the account |
| `applications.accounts.failed` | The application ended without an account |

Gravv sends no event for a block that waits on nobody, such as the bank's own review. Gravv sends each type once for one application, so it sends nothing for a second block of another kind. After you act on a block, read the application again instead of waiting for another event.

| Field | Type | Description |
| :-- | :-- | :-- |
| `application_id` | string | The application |
| `account_id` | string | The account it opens |
| `customer_id` | string | The customer |
| `tenant_id` | string | Your business |
| `type` | string | The event type again, such as `applications.accounts.blocked` |
| `status` | string | `pending`, `blocked`, `completed` or `failed` |
| `created_at`, `updated_at` | string | Timestamps. The envelope `timestamp` is `updated_at` |
| `metadata` | object or null | `null` on `pending` and `completed`. On `blocked`: `block_reason`, `action_url` (for `tos_pending` and `kyc_pending`), and `additional_requirements` with `fields` and `documents`. On `failed`: `fail_reason`, one of `verification_rejected`, `identity_document_mismatch`, `information_expired`, `region_not_supported`, `request_cancelled`, `review_declined` or `verification_declined` |

```json title="applications.accounts.blocked"
{
  "tenant_id": "5d0c9b8a-7f6e-4d5c-8b4a-3f2e1d0c9b8a",
  "transaction_id": "",
  "event_category": "applications.accounts",
  "event_type": "applications.accounts.blocked",
  "event_group_id": "5e7a9c1b-2d4f-4a6b-8c0d-e1f2a3b4c5d6",
  "timestamp": "2026-10-13T09:20:02Z",
  "event_data": {
    "account_id": "6f1c2a9e-3b1d-4f7a-9c62-0d8e4b7a5c11",
    "application_id": "5e7a9c1b-2d4f-4a6b-8c0d-e1f2a3b4c5d6",
    "created_at": "2026-10-13T09:12:44Z",
    "customer_id": "0b6f3c1e-2a4d-4c8e-9f10-7d2a6b5e4c33",
    "metadata": {
      "additional_requirements": {
        "documents": ["proof_of_address"],
        "fields": []
      },
      "block_reason": "awaiting_customer_document"
    },
    "status": "blocked",
    "tenant_id": "5d0c9b8a-7f6e-4d5c-8b4a-3f2e1d0c9b8a",
    "type": "applications.accounts.blocked",
    "updated_at": "2026-10-13T09:20:02Z"
  },
  "event_id": "2f4a6c8e-0b1d-4f3a-8c5e-7a9b1d3f5a7c",
  "idempotency_key": "4a3b2c1d0e9f8a7b6c5d4e3f2a1b0c9d8e7f6a5b4c3d2e1f0a9b8c7d6e5f4a3b"
}
```

On `completed`, read [`GET /v2/accounts/{id}`](/platform/accounts/list-and-read-accounts) for the bank details.

## Accounts Gravv registers for you

Gravv sends `account.activated` once, when an existing bank account that Gravv registered for you becomes active. Gravv never sends it for a v2 account you open yourself, so wait for `applications.accounts.completed` instead. The category is `account`, and `event_group_id` is the account id.

| Field | Type | Description |
| :-- | :-- | :-- |
| `account_id` | string | The account |
| `customer_id` | string | The customer it belongs to |
| `status` | string | `active` |
| `currency` | string | The account currency |
| `bank_details` | object | Deposit details. USD: `bank_name`, `account_name`, `account_number`, `routing_number`, `bank_address`, `beneficiary_address`. EUR: `bank_name`, `account_name`, `iban`, `bic`, `bank_address`, `beneficiary_address`. GBP: the EUR keys plus `account_number` and `sort_code`. Other currencies: `bank_name`, `account_name`, `account_number`. Empty values are left out |
| `wallet_details` | object | `address` and `network`, only when the account settles to a wallet |
| `created_at` | string | When Gravv created the event |

```json title="account.activated"
{
  "tenant_id": "dfa96ede-fc13-43cf-8328-8b910d1cd1d2",
  "transaction_id": "",
  "event_category": "account",
  "event_type": "account.activated",
  "event_group_id": "4f3e2d1c-0b9a-4876-9543-210fedcba987",
  "timestamp": "2026-10-12T16:40:05Z",
  "event_data": {
    "account_id": "4f3e2d1c-0b9a-4876-9543-210fedcba987",
    "bank_details": {
      "account_name": "Acme Ltd",
      "account_number": "9876543210",
      "bank_name": "Example Bank",
      "routing_number": "021000021"
    },
    "created_at": "2026-10-12T16:40:05.118Z",
    "currency": "USD",
    "customer_id": "0ff6cf9a-8da0-466d-a71c-714eb4bde248",
    "status": "active"
  },
  "event_id": "5e4d3c2b-1a09-4f8e-a7d6-c5b4a3928170",
  "idempotency_key": "0e9d8c7b6a5f4e3d2c1b0a9f8e7d6c5b4a3f2e1d0c9b8a7f6e5d4c3b2a1f0e9d"
}
```

## External accounts

Gravv sends these events when you add an [external account](/platform/external-accounts/overview) and when its setup finishes. The category is `external_account`. `event_group_id` is the external account's `id`, and `idempotency_key` is `<id>:<event type>:<revision>`.

A `bank_account` or `mobile_money` external account is `active` as soon as you add it, so Gravv sends only `external_account.created` for it. An `ach_wire`, `sepa`, `swift` or `fps` external account starts `pending`, and Gravv sets it up in the background.

| Event type | Sent when |
| :-- | :-- |
| `external_account.created` | An external account was added, with its status at that moment: `pending` or `active` |
| `external_account.active` | Setup of a `pending` external account succeeded. You can now send transfers to it |
| `external_account.setup_failed` | Setup did not succeed, and you can pay only an `active` external account. `error_message` says why. Correct the details with [Update an external account](/platform/external-accounts/update-an-external-account) |

`event_data` is the external account, with the fields that [Get an external account](/api-reference/external-accounts/get-v1-external-accounts-external-account-id) returns. Gravv leaves out fields with no value. On `setup_failed`, `error_message` says why. Show your own message to your users, not this text.

```json title="external_account.active"
{
  "tenant_id": "dfa96ede-fc13-43cf-8328-8b910d1cd1d2",
  "transaction_id": "",
  "event_category": "external_account",
  "event_type": "external_account.active",
  "event_group_id": "c2f5a0b1-8d3e-4a6f-9b7c-0e1d2f3a4b5c",
  "timestamp": "2026-10-12T14:05:51.204377Z",
  "event_data": {
    "account_name": "ANNA SCHMIDT",
    "account_owner_type": "individual",
    "currency": "EUR",
    "customer_id": "0ff6cf9a-8da0-466d-a71c-714eb4bde248",
    "date_created": "2026-10-12T14:02:10.551Z",
    "iban": "DE89370400440532013000",
    "id": "c2f5a0b1-8d3e-4a6f-9b7c-0e1d2f3a4b5c",
    "institution_country_iso_code": "DE",
    "payee_type": "sepa",
    "status": "active"
  },
  "event_id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
  "idempotency_key": "c2f5a0b1-8d3e-4a6f-9b7c-0e1d2f3a4b5c:external_account.active:2"
}
```

## Cards

Gravv sends these events for [cards](/platform/cards/overview) you issue. The category is `cards`. `event_group_id` is the card id on every card event, including payments. There is no `idempotency_key`, so Gravv does not send an event identical to an earlier one again.

| Event type | Sent when | `event_data` |
| :-- | :-- | :-- |
| `cards.application.update` | The card application changed status | `application`: `id` (the card's id), `status` and `customer_id`. `approved` means the card can be issued. `pending`, `manual_review`, `needs_information` and `needs_verification` mean it is still in review. `status` is empty when none is set |
| `cards.status.update` | The card changed status | `card` (below) |
| `cards.transaction.payment` | A card payment was recorded or changed status | `card` and `transaction` (below) |
| `cards.transaction.funding` | Money was added to the card | `card` and `transaction` |
| `cards.transaction.withdraw` | Money was taken off the card | `card` and `transaction` |

`card` holds `id`, `customer_id`, `name_on_card`, `last4`, `expiry`, `network` and `status`, all strings. `status` is `active`, `freeze`, `blocked` or `inactive`.

`transaction` holds:

| Field | Type | Description |
| :-- | :-- | :-- |
| `id` | string | The card transaction |
| `transaction_type` | string | `card_payment`, `card_funding` or `card_withdrawal` |
| `status` | string | `pending`, `completed` or `failed` |
| `amount` | string | The amount, such as `"18.75"` |
| `currency` | string | `USD` |
| `client_reference` | string | Only on a funding you sent as a transfer: your `client_reference` |
| `reference` | string | On a funding you sent as a transfer, the transfer's `reference`. Do not build on it for other types |
| `tx_hash` | string | Only when the movement has an on-chain hash |

A payment event carries no merchant or cardholder details, and a declined payment carries no reason. See [Card decline reasons](/platform/cards/card-decline-reasons). Do not wait for `cards.transaction.funding` to confirm a funding. Follow the transfer and the transaction instead, as [Fund cards](/platform/cards/fund-cards) explains.

```json title="cards.transaction.payment"
{
  "tenant_id": "dfa96ede-fc13-43cf-8328-8b910d1cd1d2",
  "transaction_id": "",
  "event_category": "cards",
  "event_type": "cards.transaction.payment",
  "event_group_id": "6a5b4c3d-2e1f-4a0b-9c8d-7e6f5a4b3c2d",
  "timestamp": "2026-10-13T10:22:31.902117Z",
  "event_data": {
    "card": {
      "customer_id": "0ff6cf9a-8da0-466d-a71c-714eb4bde248",
      "expiry": "09/29",
      "id": "6a5b4c3d-2e1f-4a0b-9c8d-7e6f5a4b3c2d",
      "last4": "4242",
      "name_on_card": "JANE DOE",
      "network": "visa",
      "status": "active"
    },
    "transaction": {
      "amount": "18.75",
      "currency": "USD",
      "id": "f1e2d3c4-b5a6-4978-8a6b-5c4d3e2f1a0b",
      "status": "completed",
      "transaction_type": "card_payment"
    }
  },
  "event_id": "0f1e2d3c-4b5a-4697-8877-665544332211"
}
```

## Card disputes

Gravv sends these events when a payment on one of your cards is disputed. These docs do not cover an API to open or respond to a dispute, so treat these events as notices for your records and your support team. The category is `cards`. `event_group_id` is the card id, and the top-level `transaction_id` is the disputed transaction.

| Event type | Sent when |
| :-- | :-- |
| `cards.dispute.submitted` | The dispute was filed |
| `cards.dispute.updated` | The dispute is under review, or it moved to a new stage |
| `cards.dispute.resolved` | The dispute ended: `won`, `lost`, `withdrawn` or `expired` |

| Field | Type | Description |
| :-- | :-- | :-- |
| `id` | string | The dispute |
| `card_id` | string | The card |
| `transaction_id` | string | The disputed card transaction |
| `status` | string | `submitted`, `under_review`, `won`, `lost`, `withdrawn` or `expired` |
| `reason` | string | `fraud`, `billing_error`, `duplicate` or `not_received` |
| `amount` | string | The disputed amount |
| `currency` | string | The currency |
| `stage` | string | `representment`, `second_presentment`, `pre_arbitration` or `arbitration`, when set |
| `resolution_amount` | string | The amount decided, once known |
| `evidence_due_by` | string | When evidence is due, when set |
| `resolved_at` | string | When the dispute ended |

```json title="cards.dispute.resolved"
{
  "tenant_id": "dfa96ede-fc13-43cf-8328-8b910d1cd1d2",
  "transaction_id": "f1e2d3c4-b5a6-4978-8a6b-5c4d3e2f1a0b",
  "event_category": "cards",
  "event_type": "cards.dispute.resolved",
  "event_group_id": "6a5b4c3d-2e1f-4a0b-9c8d-7e6f5a4b3c2d",
  "timestamp": "2026-10-13T15:10:44Z",
  "event_data": {
    "amount": "18.75",
    "card_id": "6a5b4c3d-2e1f-4a0b-9c8d-7e6f5a4b3c2d",
    "currency": "USD",
    "id": "7b2e9c41-5d3a-4f8e-a1b6-0c9d8e7f6a5b",
    "reason": "not_received",
    "resolution_amount": "18.75",
    "resolved_at": "2026-10-13T15:10:44Z",
    "status": "won",
    "transaction_id": "f1e2d3c4-b5a6-4978-8a6b-5c4d3e2f1a0b"
  },
  "event_id": "3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f"
}
```

## Collections

Gravv sends these events as a [payment collection](/platform/collections/collections-overview) moves from the payer's payment to the stablecoin reaching your destination. The category is `collection`. `event_group_id` and the top-level `transaction_id` are the collection's `transaction_id`.

<Note>
On or after 31 October 2026, the values in `onramp_status` change and Gravv renames two event types. If you read `new_onramp_status` now, your integration keeps working through the change. See [Status label change](#status-label-change).
</Note>

| Event type | Sent when |
| :-- | :-- |
| `collection.status.pending` | The collection is in progress. Also sent, with `onramp_status` `expired` or `cancelled`, when it expires or is cancelled |
| `collection.status.awaiting_capture` | A card payment is authorized and waits for capture |
| `collection.status.provider_success` | The payment succeeded and settlement has not finished |
| `collection.status.completed` | The money reached your destination |
| `collection.status.failed` | The payment failed, the payer abandoned it, or a refund failed |
| `collection.status.provider_voided` | A card authorization was voided. `transaction_status` is `cancelled` |
| `collection.status.refund_pending` | A refund has started |
| `collection.status.refunded` | The payment was refunded |
| `collection.status.disputed` | The payer opened a chargeback, or a chargeback was lost |

Gravv sends no event when you create a collection, and the first event can arrive several minutes later. Events can arrive out of order. The step where a payment succeeds can arrive as `collection.status.pending` with `new_onramp_status` `payment_succeeded`. So build on `transaction_status` and the status label, not on the event type or the order of arrival. Confirm the end state with `GET /v1/collections/{id}`.

| Field | Type | Description |
| :-- | :-- | :-- |
| `transaction_id` | string | The collection's id. Use it with `GET /v1/collections/{id}` |
| `transaction_status` | string | `pending`, `completed`, `failed`, `refunded`, `refund_pending`, `disputed` or `cancelled` |
| `onramp_status` | string | The step, with the label in use until the change. See [Status label change](#status-label-change) |
| `new_onramp_status` | string | The step, with the new label. Sent until the change |
| `status_transition_deadline` | string | `2026-10-31T23:59:59Z`. Sent until the change |
| `amount` | string | The amount collected, with two decimals |
| `asset`, `network` | string | The stablecoin you receive (`USDC` or `USDT`) and its network. Can be empty before settlement |
| `tx_hash` | string | The settlement hash, once the money has settled |
| `payment_method` | string | How the payer paid, such as `card`, when known |
| `payment_intent_id` | string | The card payment intent, for collections made through one |
| `payment_info` | object | For card payments: `bin`, `exp_month`, `exp_year`, `card_brand`, `card_type`, `card_segment`, `last4`, each when known |
| `error_code` | string | A failure code, such as `INSUFFICIENT_FUNDS`, `CARD_EXPIRED` or `THREE_DS_FAILED`, when known. A decline simulated in sandbox carries none |
| `error_message`, `remark` | string | A readable failure description, when known |
| `client_reference`, `reference` | string | Your reference from the collection request |
| `metadata` | object | The data you attached to the collection request |
| `customer_id` | string | The Gravv customer the collection belongs to |
| `client_customer_id` | string | Your own customer id, when you passed one |
| `tenant_id` | string | Your business |

Credit your customer once per `transaction_id`, never once per event. The same step can arrive twice with different `event_id` values. Treat a second event with the same `transaction_id` and status as a duplicate.

### Status label change

Every collection event, and every collection the API returns, carries two status labels until 31 October 2026:

- `onramp_status` carries the current label, kept for existing integrations.
- `new_onramp_status` carries the new label.
- `status_transition_deadline` is `2026-10-31T23:59:59Z`, the date after which `onramp_status` takes the new labels.

On or after 31 October 2026:

- `onramp_status` carries the new labels, the values `new_onramp_status` carries today.
- Gravv stops sending `new_onramp_status` and `status_transition_deadline`.
- `collection.status.provider_success` becomes `collection.status.payment_succeeded`.
- `collection.status.provider_voided` becomes `collection.status.payment_voided`.

What to do now:

1. Match on `new_onramp_status` when it is present, and on `onramp_status` when it is not.
2. Accept both names of the two renamed event types.
3. When you filter collections by `onramp_status`, either label works.

| Label in `onramp_status` until the change | Label in `new_onramp_status`, and in `onramp_status` after it | Meaning |
| :-- | :-- | :-- |
| `provider_initiated` | `payment_initiated` | The payer started paying |
| `provider_success` | `payment_succeeded` | The payment succeeded |
| `provider_failed` | `payment_failed` | The payment failed |
| `provider_voided` | `payment_voided` | A card authorization was voided |
| `transfer_queued` | `settlement_queued` | Settlement to your destination is queued |
| `transfer_initialized` | `settlement_initiated` | Settlement has started |
| `transfer_success` | `settlement_succeeded` | Settlement succeeded |
| `transfer_failed` | `settlement_failed` | Settlement failed. Gravv retries it and does not send this step as an event |
| `transfer_completed` | `settlement_completed` | The money reached your destination |

These labels do not change: `created`, `initialize`, `pending`, `awaiting_capture`, `abandoned`, `expired`, `cancelled`, `pending_review`, `suspected_fraud`, `chargeback_opened`, `chargeback_won`, `chargeback_lost`, `refund_initiated`, `refunded` and `refund_failed`.

```json title="collection.status.failed"
{
  "tenant_id": "dfa96ede-fc13-43cf-8328-8b910d1cd1d2",
  "transaction_id": "2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f",
  "event_category": "collection",
  "event_type": "collection.status.failed",
  "event_group_id": "2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f",
  "timestamp": "2026-10-13T11:04:27Z",
  "event_data": {
    "amount": "1500.00",
    "asset": "",
    "customer_id": "0ff6cf9a-8da0-466d-a71c-714eb4bde248",
    "error_code": "INSUFFICIENT_FUNDS",
    "error_message": "The card has insufficient funds",
    "network": "",
    "new_onramp_status": "payment_failed",
    "onramp_status": "provider_failed",
    "payment_info": {
      "bin": "424242",
      "card_brand": "visa",
      "last4": "4242"
    },
    "payment_intent_id": "pi_7f6e5d4c3b2a",
    "payment_method": "card",
    "remark": "The card has insufficient funds",
    "status_transition_deadline": "2026-10-31T23:59:59Z",
    "tenant_id": "dfa96ede-fc13-43cf-8328-8b910d1cd1d2",
    "transaction_id": "2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f",
    "transaction_status": "failed"
  },
  "event_id": "77665544-3322-4110-8ffe-ddccbbaa9988",
  "idempotency_key": "8b7a6c5d4e3f2a1b0c9d8e7f6a5b4c3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8a7b"
}
```

## Customers and KYC

Gravv sends these events when you create a [customer](/getting-started/onboard-your-first-customer) and when their verification changes. The category is `customer`. `event_group_id` is the customer id. There is no `idempotency_key`.

| Event type | Sent when |
| :-- | :-- |
| `customer.created` | A customer was created, or an associated person was added to a business, either at creation or by a later update. Gravv sends one event for each person |
| `customer.kyc.status.pending` | Verification started, or is still under way. It can arrive more than once |
| `customer.kyc.status.completed` | Verification passed |
| `customer.kyc.status.failed` | Verification did not pass |

Gravv sends the KYC events for people and for businesses. Gravv verifies a business only after each of its associated persons, so the persons' events come first.

**`customer.created`** carries `id`, `type` (`individual`, `business`, or a person's role such as `ubo`), `status`, `email`, `address` and `created_at`, and a `kyc` list with `basic` and `intermediate`, both `not_started`. It carries no names and no phone number. Read them with [`GET /v1/customers/{customer_id}`](/api-reference/customers/get-v1-customers-customer-id).

**The KYC events** carry:

| Field | Type | Description |
| :-- | :-- | :-- |
| `customer_id` | string | The customer whose verification changed |
| `tenant_id` | string | Your business |
| `kyc_type` | string | The level, `basic` |
| `status` | string | `pending`, `completed` or `failed` |
| `customer_email` | string | The customer's email |
| `customer_type` | string | `individual` or `business` |
| `applicant_id` | string | An internal reference. Ignore it, and do not show it to users |
| `completed_at` | string | When verification passed. Only on `completed` |
| `failure_reason` | string | Only on `failed`. Show your own message, not this text |
| `business_name`, `business_type`, `registration_number` | string | Only for a business |

Gravv sends no `pending` event when the card KYC check ([`/v1/risk/*`](/platform/cards/card-kyc/overview)) starts. Its events can also carry `country`, `city`, `state`, `postal_code` and `business_address`.

Gravv does not guarantee the order. Before you act, read the customer. `kyc[type=basic].status` on [`GET /v1/customers/{customer_id}`](/api-reference/customers/get-v1-customers-customer-id) is the source of truth.

```json title="customer.kyc.status.completed"
{
  "tenant_id": "5d0c9b8a-7f6e-4d5c-8b4a-3f2e1d0c9b8a",
  "transaction_id": "",
  "event_category": "customer",
  "event_type": "customer.kyc.status.completed",
  "event_group_id": "3f6b2a1e-8c4d-4e7f-9a2b-5c1d0e9f8a71",
  "timestamp": "2026-10-13T10:01:03Z",
  "event_data": {
    "applicant_id": "<internal reference>",
    "completed_at": "2026-10-13T10:01:03Z",
    "customer_email": "amara.nwosu@example.com",
    "customer_id": "3f6b2a1e-8c4d-4e7f-9a2b-5c1d0e9f8a71",
    "customer_type": "individual",
    "kyc_type": "basic",
    "status": "completed",
    "tenant_id": "5d0c9b8a-7f6e-4d5c-8b4a-3f2e1d0c9b8a"
  },
  "event_id": "0b6e2f4a-8c1d-4e3f-9a5b-7c2d4e6f8a10"
}
```

## Feature access

Gravv sends these events when your request for a feature, such as card issuing, moves forward. A request to a feature that is not enabled for you returns `403` with a `feature_access_*` code. See [Feature access errors](/getting-started/errors#feature-access-errors). These docs do not cover an API to request a feature or to respond to Gravv, so these events tell you when to look in the Dashboard. The category is `feature_access`.

| Event type | Sent when |
| :-- | :-- |
| `feature_access.requested` | You sent a request, or answered a request for information (then `resubmitted` is `true`) |
| `feature_access.action_required` | Gravv needs more information. `requirements` lists what it needs |
| `feature_access.approved` | The request was approved |
| `feature_access.rejected` | The request was rejected. `reason` says why, and `reapply_after` says when you can ask again |
| `feature_access.suspended` | Gravv suspended the feature for your business. `reason` says why |

- `event_group_id` is the request's id, or your business id for a suspension.
- `idempotency_key` is `<request_id>:<event type>`, or `<business id>:<feature>:<event type>` for a suspension. So Gravv does not send a second suspension of the same feature.

| Field | Type | Description |
| :-- | :-- | :-- |
| `feature` | string | `card_issuance`, `card_acquiring` or `otc_trade_desk` |
| `feature_name` | string | The display name, such as `Card Issuance` |
| `status` | string | The feature's new access status: `locked`, `pending`, `action_required`, `rejected`, `enabled` or `suspended` |
| `request_id` | string | Your request's reference, such as `far_7k29pqvd`. Left out for a suspension |
| `reason` | string | Only on `rejected` and `suspended` |
| `reapply_after` | string | The earliest time you can send a rejected request again, when there is a wait |
| `requirements` | array of strings | Only on `action_required`. Codes for what is needed. The Dashboard shows the full text |
| `resubmitted` | boolean | `true` when `requested` is your response to `action_required` |
| `occurred_at` | string | When it happened. The envelope `timestamp` is the same |

```json title="feature_access.action_required"
{
  "tenant_id": "dfa96ede-fc13-43cf-8328-8b910d1cd1d2",
  "transaction_id": "",
  "event_category": "feature_access",
  "event_type": "feature_access.action_required",
  "event_group_id": "5d2c7a10-3b4e-4f6a-9c8d-1e2f3a4b5c6d",
  "timestamp": "2026-10-13T09:41:07Z",
  "event_data": {
    "feature": "card_issuance",
    "feature_name": "Card Issuance",
    "occurred_at": "2026-10-13T09:41:07Z",
    "request_id": "far_7k29pqvd",
    "requirements": ["business_website", "expected_monthly_volume"],
    "status": "action_required"
  },
  "event_id": "e4d3c2b1-a098-4f7e-8d6c-5b4a39281706",
  "idempotency_key": "far_7k29pqvd:feature_access.action_required"
}
```
