> For the complete documentation index, see [llms.txt](https://docs.pay.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.pay.io/api-reference/core-concepts/event-notifications-and-webhooks.md).

# Event Notifications and Webhooks

The Pay.io Payment Gateway provides real-time event notifications, so merchants can stay up to date with deposits, withdrawals, and onramp purchases as they happen.

You can consume these events through Webhooks, the gateway pushes event notifications to your HTTP endpoint.

***

### Event Types for Webhooks

The gateway sends notifications for the following transaction events:

#### Deposit Events

* Processing Event - The deposit has been detected on the blockchain and is being processed.
* Confirmation Event - The blockchain has confirmed the transaction, funds are now available in the wallet.

#### Withdrawal Events

* Successful Event - The withdrawal was completed successfully, confirms that funds have been transferred.

#### Onramp Events

When a user buys crypto with fiat through an onramp session (created via POST /v1/user/payments), the purchase moves through several stages — the provider accepting the session, the card payment, and the on-chain delivery of funds. The gateway sends a User Onramp event at every stage, so you can show the user live purchase status and credit funds the moment they settle, without polling.

* Pending Event - The provider has picked up the session, awaiting the user's payment.
* Processing Event - The payment was accepted and the crypto transfer is in progress.
* Succeeded Event - The crypto has been delivered to the destination wallet.
* Failed Event - The transaction failed and no funds moved.

{% hint style="info" %}
One session, multiple callbacks. Deposit and withdrawal events are self-contained, but an onramp session emits a callback for each status change: pending → processing → succeeded (or failed). Group them by transaction\_uuid, and use correlation\_key to match callbacks to the session you created via POST /v1/user/payments.
{% endhint %}

***

### Webhook Integration

#### Setup Requirements

1. Webhook URL - Update the URL, which Payment Gateway will call in the Merchant Portal.
2. IP Whitelisting - Contact support for the list of IPs to allow.
3. Public Key - Obtain your Pay.io public key from the Merchant Console.
4. Secure Storage - Store the key securely in your application configuration.
5. Signature Verification - Implement verification on all incoming requests.

#### Webhook Security Verification

Each webhook request includes headers you must validate. Verification is identical for all event types — deposit, withdrawal, and onramp callbacks are all signed the same way.

\[Table: Header / Description]\
X-Signature — Base64-encoded request signature\
X-Timestamp — UNIX timestamp when the event was sent\
X-Algorithm — Signing algorithm, always RSA-SHA256

Verification steps:

1. Extract the raw JSON request payload.
2. Read X-Signature, X-Timestamp, and X-Algorithm headers.

```
signature = request.headers.get('X-Signature')
timestamp = request.headers.get('X-Timestamp')
algorithm = request.headers.get('X-Algorithm')
```

3. Build a string to verify:

```
string_to_verify = json_payload + timestamp
```

4. Decode the Base64 signature.
5. Verify with RSA-SHA256 using the stored Pay.io public key.

**Example: Verification in Elixir**

```elixir
def verify_signature(json_payload, signature, public_key_pem, timestamp) do
  case parse_public_key(public_key_pem) do
    {:ok, public_key} ->
      string_to_verify = json_payload <> to_string(timestamp)
      signature_bytes = Base.decode64!(signature)
      case :public_key.verify(string_to_verify, :sha256, signature_bytes, public_key) do
        true -> {:ok, :verified}
        false -> {:error, :signature_invalid}
      end
    {:error, reason} ->
      {:error, reason}
  end
end
```

***

### Event Structure

Each event payload includes:

* `event` - Type of event (User Deposit, User Payout, User Onramp).
* `transaction` - Full transaction details.
* `transaction_uuid` - Unique transaction ID for reconciliation.
* `user_reference` - Your internal user ID.
* `merchant_reference` - Reference configured during transaction creation (deposit and withdrawal events).
* `correlation_key` / `session_reference` - Session identifiers for matching onramp callbacks to the session you created (onramp events).

Because an onramp purchase starts on the fiat side and settles on the crypto side, its transaction object differs from deposit and withdrawal payloads:

|                                         | User Deposit / User Payout                                | User Onramp                                     |
| --------------------------------------- | --------------------------------------------------------- | ----------------------------------------------- |
| `currency` block                        | Nested object with `id`, `symbol`, `token_address`, icons | Not present                                     |
| `to_currency` block                     | Not present                                               | `{code, name, network}`                         |
| `from_amount` / `from_currency`         | Not present                                               | Present (fiat side of the purchase)             |
| `to_address` / `tx_hash`                | Present                                                   | Not present (destination is stored server-side) |
| `merchant_reference`                    | Present                                                   | Not present                                     |
| `correlation_key` / `session_reference` | Not present                                               | Present                                         |
| Multiple callbacks per session          | No (single event)                                         | Yes, one per status change                      |

**Example: Deposit Processing**

```json
{
  "event": "User Deposit",
  "transaction": {
    "id": "43dae597-f283-4631-8e73-1e40ecfaedfa",
    "status": "processing",
    "date": "2025-08-27T14:51:53Z",
    "currency": {
      "id": "c872e749-fd56-533e-b01f-de87ae38e7f1",
      "name": "USD Coin",
      "symbol": "$",
      "network": "Base Chain",
      "token_address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "currency_icon": "https://cdn.hub88.io/hub-wallet/USDC-ic.svg",
      "network_icon": "https://cdn.hub88.io/hub-wallet/BASE-ic.svg",
      "currency_code": "USDC"
    },
    "amount": 2.35,
    "provider_transaction_id": "pay_31sINaTdZeCYWCLobzX16X6QGFm",
    "to_address": "0x6d0e5b532ba0857ca391ee2d11b02c596fcded3c",
    "from_address": "0xabf7920042335cb1fd9e7c688f99822a1d879445",
    "tx_hash": "0xcd8fba9d72c3e25d55f7f63d58fffbc3411692df611e480c6975adfb2d8938cd",
    "transaction_type": "Deposit",
    "amount_usd": 2.35
  },
  "transaction_uuid": "43dae597-f283-4631-8e73-1e40ecfaedfa",
  "user_reference": "Jon14",
  "merchant_reference": "1_hubwallet-demo_6gmma63qfxdag",
  "payment_method": "Deposit"
}
```

**Example: Deposit Confirmation**

```json
{
  "event": "User Deposit",
  "transaction": {
    "id": "43dae597-f283-4631-8e73-1e40ecfaedfa",
    "status": "succeeded",
    "date": "2025-08-27T14:53:59Z",
    "currency": {
      "id": "c872e749-fd56-533e-b01f-de87ae38e7f1",
      "name": "USD Coin",
      "symbol": "$",
      "network": "Base Chain",
      "token_address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "currency_icon": "https://cdn.hub88.io/hub-wallet/USDC-ic.svg",
      "network_icon": "https://cdn.hub88.io/hub-wallet/BASE-ic.svg",
      "currency_code": "USDC"
    },
    "amount": 2.35,
    "provider_transaction_id": "pay_31sINaTdZeCYWCLobzX16X6QGFm",
    "to_address": "0x6d0e5b532ba0857ca391ee2d11b02c596fcded3c",
    "from_address": "0xabf7920042335cb1fd9e7c688f99822a1d879445",
    "tx_hash": "0xcd8fba9d72c3e25d55f7f63d58fffbc3411692df611e480c6975adfb2d8938cd",
    "transaction_type": "Deposit",
    "amount_usd": 2.35
  },
  "transaction_uuid": "43dae597-f283-4631-8e73-1e40ecfaedfa",
  "user_reference": "Jon14",
  "merchant_reference": "1_hubwallet-demo_6gmma63qfxdag",
  "payment_method": "Deposit"
}
```

**Example: Withdrawal Successful**

```json
{
  "event": "User Payout",
  "transaction": {
    "id": "492bcb88-149e-4d18-bfd4-2d2c2b29e87d",
    "status": "succeeded",
    "date": "2025-08-27T15:04:28Z",
    "currency": {
      "id": "c872e749-fd56-533e-b01f-de87ae38e7f1",
      "name": "USD Coin",
      "symbol": "$",
      "network": "Base Chain",
      "token_address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "currency_icon": "https://cdn.hub88.io/hub-wallet/USDC-ic.svg",
      "network_icon": "https://cdn.hub88.io/hub-wallet/BASE-ic.svg",
      "currency_code": "USDC"
    },
    "amount": 2.0,
    "provider_transaction_id": "pay_31sJg9321pd5yAWKOhVAIffWqVX",
    "to_address": "0xabf7920042335cB1FD9e7C688F99822a1D879445",
    "from_address": "0x09b8c5b24bcdd91af5a7d32f6a4ae9fa4183d4f4",
    "tx_hash": "0x76088442e6ce9524ead531be4296a379cf772dc96cddc9cbb9b2b01d5f0be7fa",
    "transaction_type": "Payout",
    "amount_usd": 1.9804985390232988
  },
  "transaction_uuid": "492bcb88-149e-4d18-bfd4-2d2c2b29e87d",
  "user_reference": "Jon14",
  "merchant_reference": "1_hubwallet-demo_6gmma63qfxdag",
  "payment_method": "Payout"
}
```

**Example: Onramp Pending**

The provider has picked up the session. `provider_transaction_id` and `payment_method` are still `null` at this stage — they populate once the user completes their payment.

```json
{
  "event": "User Onramp",
  "transaction_uuid": "d234aeb2-18c7-42d4-a441-01e028f45195",
  "correlation_key": "f47ac10b-58cc-4372-a567-0e02b2c3d479:order_9981",
  "session_reference": "order_9981",
  "user_reference": "player_42",
  "payment_method": null,
  "transaction": {
    "id": "d234aeb2-18c7-42d4-a441-01e028f45195",
    "provider_transaction_id": null,
    "transaction_type": "onramp",
    "date": "2026-06-26T14:17:01Z",
    "status": "pending",
    "to_amount": null,
    "to_currency": {
      "code": "USDC",
      "name": "USD Coin",
      "network": "Base Chain"
    },
    "from_amount": null,
    "from_currency": null
  }
}
```

**Example: Onramp Processing**

The user has paid, and the crypto transfer is in progress. The fiat side (`from_amount`, `from_currency`) is now populated; `to_amount` stays `null` until the funds settle.

```json
{
  "event": "User Onramp",
  "transaction_uuid": "d234aeb2-18c7-42d4-a441-01e028f45195",
  "correlation_key": "f47ac10b-58cc-4372-a567-0e02b2c3d479:order_9981",
  "session_reference": "order_9981",
  "user_reference": "player_42",
  "payment_method": "CREDIT_DEBIT_CARD",
  "transaction": {
    "id": "d234aeb2-18c7-42d4-a441-01e028f45195",
    "provider_transaction_id": "WmYukCEa2AyP5T25s9DHn2",
    "transaction_type": "onramp",
    "date": "2026-06-26T14:18:22Z",
    "status": "processing",
    "to_amount": null,
    "to_currency": {
      "code": "USDC",
      "name": "USD Coin",
      "network": "Base Chain"
    },
    "from_amount": 250.0,
    "from_currency": "EUR"
  }
}
```

**Example: Onramp Succeeded**

The crypto has been delivered to the destination wallet. `to_amount` now shows the final settled amount, as this is the event to act on when crediting the user.

```json
{
  "event": "User Onramp",
  "transaction_uuid": "d234aeb2-18c7-42d4-a441-01e028f45195",
  "correlation_key": "f47ac10b-58cc-4372-a567-0e02b2c3d479:order_9981",
  "session_reference": "order_9981",
  "user_reference": "player_42",
  "payment_method": "CREDIT_DEBIT_CARD",
  "transaction": {
    "id": "d234aeb2-18c7-42d4-a441-01e028f45195",
    "provider_transaction_id": "WmYukCEa2AyP5T25s9DHn2",
    "transaction_type": "onramp",
    "date": "2026-06-26T14:19:43Z",
    "status": "succeeded",
    "to_amount": 271.53,
    "to_currency": {
      "code": "USDC",
      "name": "USD Coin",
      "network": "Base Chain"
    },
    "from_amount": 250.0,
    "from_currency": "EUR"
  }
}
```

**Example: Onramp Failed**

The transaction failed and no funds moved. No action is needed on your side beyond updating the user's purchase status.

```json
{
  "event": "User Onramp",
  "transaction_uuid": "d234aeb2-18c7-42d4-a441-01e028f45195",
  "correlation_key": "f47ac10b-58cc-4372-a567-0e02b2c3d479:order_9981",
  "session_reference": "order_9981",
  "user_reference": "player_42",
  "payment_method": "CREDIT_DEBIT_CARD",
  "transaction": {
    "id": "d234aeb2-18c7-42d4-a441-01e028f45195",
    "provider_transaction_id": "WmYukCEa2AyP5T25s9DHn2",
    "transaction_type": "onramp",
    "date": "2026-06-26T14:19:43Z",
    "status": "failed",
    "to_amount": null,
    "to_currency": {
      "code": "USDC",
      "name": "USD Coin",
      "network": "Base Chain"
    },
    "from_amount": 250.0,
    "from_currency": "EUR"
  }
}
```

**Onramp Field Reference**

| Field                                 | Type              | Description                                                                                    |
| ------------------------------------- | ----------------- | ---------------------------------------------------------------------------------------------- |
| `event`                               | string            | Always `"User Onramp"`                                                                         |
| `transaction_uuid`                    | string (UUID)     | Internal session ID                                                                            |
| `correlation_key`                     | string            | `{merchant_id}:{session_reference}` — matches the value returned from `POST /v1/user/payments` |
| `session_reference`                   | string \| null    | Your own ID for the session, if you supplied it at creation                                    |
| `user_reference`                      | string \| null    | Player ID, if you supplied it at creation                                                      |
| `payment_method`                      | string \| null    | e.g. `"CREDIT_DEBIT_CARD"`. Null on the first `pending` event                                  |
| `transaction.status`                  | string            | `pending` \| `processing` \| `succeeded` \| `failed`                                           |
| `transaction.transaction_type`        | string            | Always `"onramp"`                                                                              |
| `transaction.to_amount`               | number \| null    | Crypto amount received; null until settled                                                     |
| `transaction.to_currency`             | object            | `{code, name, network}`                                                                        |
| `transaction.from_amount`             | number \| null    | Fiat amount paid; populated from `processing` onwards                                          |
| `transaction.from_currency`           | string \| null    | Fiat currency code, e.g. `"EUR"`                                                               |
| `transaction.provider_transaction_id` | string \| null    | Provider's ID; null on the first `pending` event                                               |
| `transaction.date`                    | string (ISO 8601) | Timestamp of this status change                                                                |


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.pay.io/api-reference/core-concepts/event-notifications-and-webhooks.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
