> ## Documentation Index
> Fetch the complete documentation index at: https://docs.portalhq.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Alert Webhooks

> Get realtime wallet notifications for your Portal clients.

## What's an alert webhook?

Alert webhooks can send you realtime wallet notifications for your clients. For example, they can be useful for receiving notifications when your clients receive or send EVM transactions. Alert webhooks are easily configured in the Portal Admin Dashboard. If you don't see "Alert Webhooks" in the Portal Admin Dashboard, reach out to our team and we can enable the feature for you.

### Configuring alert webhooks

1. Reach out to our support team and we can enable alert webhooks for your organization.
2. Navigate to `"Configuration"` > `"Webhooks"` in the [Portal Admin Dashboard](https://app.portalhq.io) and under the `"Alert Webhooks"` section, click `"New +"`.
3. Enter your alert webhook URL.
4. Select the events you want to receive (e.g. `EVM Wallet Transactions`).
5. Save your configuration.

<Note>
  Take note of the IP addresses listed on this modal. You can add them to your allowlist to ensure you're only accepting requests from Portal.
</Note>

### External addresses

External addresses let you receive alert webhook notifications for blockchain addresses that weren't generated by Portal — such as treasury wallets, exchange deposit addresses, or other addresses you want to monitor. Portal supports external addresses on **EVM (EIP-155)** and **Solana** namespaces.

#### Adding external addresses via the Dashboard

1. Navigate to `"Configuration"` > `"Webhooks"` in the [Portal Admin Dashboard](https://app.portalhq.io).
2. Below the `"Alert Webhooks"` section, find the `"External Addresses"` section.
3. Click `"Add"`.
4. Select a namespace (`eip155` or `solana`).
5. Enter the address you want to monitor.

#### Adding external addresses via the API

You can also manage external addresses programmatically using the Custodian API:

* **Create:** `POST /custodians/me/alerts/webhooks/external-addresses`
* **List:** `GET /custodians/me/alerts/webhooks/external-addresses`
* **Delete:** `DELETE /custodians/me/alerts/webhooks/external-addresses/{externalAddressId}`

The request body for creating an external address requires an `address` and a `namespace`:

```json theme={null}
{
  "address": "0x1234...",
  "namespace": "eip155"
}
```

For Solana addresses, use `"solana"` as the namespace:

```json theme={null}
{
  "address": "ABC123...",
  "namespace": "solana"
}
```

<Note>
  Addresses are validated for correct format based on the selected namespace. Blackhole addresses (such as null or dead addresses) are rejected.
</Note>

## Dual events for internal transfers

When **both** the sender and the receiver of an on-chain transfer are addresses you've registered with Portal — for example, two of your Portal clients, or a Portal client transferring to one of your [external addresses](#external-addresses) — a single on-chain transfer produces **two webhook events**, not one:

* An **`OUTBOUND`** event from the sender's perspective.
* An **`INBOUND`** event from the receiver's perspective.

Both events describe the same on-chain movement, so the `from` and `to` fields are **identical between the two events**. These fields reflect who actually sent and who actually received funds on-chain — they are *not* relative to the subscribed wallet that triggered the event.

To tell the two events apart, use:

* **`direction`** — `OUTBOUND` means the event is from the sender's perspective, `INBOUND` means it's from the receiver's perspective. On `Solana Delegated Transfers` a third value, `DELEGATED`, can also appear: it marks the owner whose tokens were moved by a delegate (the source of the funds) — an address that isn't directly executing the transaction. See [Direction](#direction).
* **`triggeredBy`** — the subscribed address that caused this particular event to be emitted. For EVM events, use `data[].metadata.triggeredBy`. For Solana events, use `data.rawEvents[].triggeredBy`. For the `OUTBOUND` event this matches the sender; for the `INBOUND` event it matches the receiver; for a `DELEGATED` event it matches the owner whose funds were moved.

<Note>
  If only one side of the transfer is registered with Portal, you'll receive a **single** event for that side. The dual-event behavior only happens when both sender and receiver are registered.
</Note>

### Example: a single transfer producing two events

The example below shows a single `1 USDC` transfer on Ethereum Sepolia from `0x04e15c...14e4` to `0x14cea4...75f0`, where **both** addresses are registered with Portal. Notice that `from`, `to`, `amount`, `transactionHash`, and `blockNumber` are identical across the two events; only `direction` and `data[].metadata.triggeredBy` differ.

<Tabs>
  <Tab title="OUTBOUND (sender's perspective)">
    ```json theme={null}
    {
      "data": [
        {
          "to": "0x14cea4af1ad06721ad4ae04ab1233e25e63575f0",
          "from": "0x04e15c61b2a9c8c54f29c9bbcb83c3e3826514e4",
          "amount": "1",
          "chainId": "eip155:11155111",
          "assetType": "NON_NATIVE_TOKEN",
          "chainName": "sepolia",
          "direction": "OUTBOUND",
          "tokenSymbol": "USDC",
          "metadata": {
            "sentAt": "2025-03-17T18:32:00.000Z",
            "confirmed": true,
            "rawAmount": "1000000",
            "tokenName": "USDC",
            "blockNumber": "12345678",
            "triggeredBy": "0x04e15c61b2a9c8c54f29c9bbcb83c3e3826514e4",
            "tokenAddress": "0x1c7d4b196cb0c7b01d743fbc6116a902379c7238",
            "tokenDecimals": 6,
            "transactionHash": "0xabc123def4567890abc123def4567890abc123def4567890abc123def4567890",
            "userOperationHash": null
          }
        }
      ],
      "metadata": {
        "custodianId": "custodianId",
        "environmentId": "environmentId"
      },
      "type": "EIP_155_TX_V1"
    }
    ```
  </Tab>

  <Tab title="INBOUND (receiver's perspective)">
    ```json theme={null}
    {
      "data": [
        {
          "to": "0x14cea4af1ad06721ad4ae04ab1233e25e63575f0",
          "from": "0x04e15c61b2a9c8c54f29c9bbcb83c3e3826514e4",
          "amount": "1",
          "chainId": "eip155:11155111",
          "assetType": "NON_NATIVE_TOKEN",
          "chainName": "sepolia",
          "direction": "INBOUND",
          "tokenSymbol": "USDC",
          "metadata": {
            "sentAt": "2025-03-17T18:32:00.000Z",
            "confirmed": true,
            "rawAmount": "1000000",
            "tokenName": "USDC",
            "blockNumber": "12345678",
            "triggeredBy": "0x14cea4af1ad06721ad4ae04ab1233e25e63575f0",
            "tokenAddress": "0x1c7d4b196cb0c7b01d743fbc6116a902379c7238",
            "tokenDecimals": 6,
            "transactionHash": "0xabc123def4567890abc123def4567890abc123def4567890abc123def4567890",
            "userOperationHash": null
          }
        }
      ],
      "metadata": {
        "custodianId": "custodianId",
        "environmentId": "environmentId"
      },
      "type": "EIP_155_TX_V1"
    }
    ```
  </Tab>
</Tabs>

### Correlating or deduplicating dual events

If your application treats internal transfers as a single business-level "movement" rather than two independent events, correlate the two events using fields that come from the underlying on-chain transaction:

| Event type                   | Correlation key                                                   |
| ---------------------------- | ----------------------------------------------------------------- |
| `EVM Wallet Transactions`    | `data[].metadata.transactionHash` + `data[].metadata.blockNumber` |
| `Solana Wallet Transactions` | `data.rawEvents[].signature`                                      |

Two events sharing the same correlation key represent the same on-chain transfer. Use `direction` and `triggeredBy` to know which subscribed wallet each event was emitted for. For EVM events, use `data[].metadata.triggeredBy`; for Solana events, use `data.rawEvents[].triggeredBy`.

<Note>
  This dual-event behavior applies to **EVM Wallet Transactions** and **Solana Wallet Transactions**. It also applies to token approvals, revocations, and delegated transfers when the subscribed addresses are on both sides of the operation.
</Note>

## Delivery event lifecycle

Each time Portal sends an alert webhook to your URL, the attempt is recorded as a **delivery event**. You can inspect every delivery event for a given alert webhook with [`GET /custodians/me/alerts/webhooks/{alertWebhookId}/delivery-events`](/api-reference/alert-webhooks/list-alert-webhook-delivery-events) and manually retry a single delivery event with [`POST /custodians/me/alerts/webhooks/{alertWebhookId}/delivery-events/{deliveryEventId}/retries`](/api-reference/alert-webhooks/retry-an-alert-webhook-delivery-event).

A delivery event always has one of four statuses:

| Status      | Meaning                                                                                                       |
| ----------- | ------------------------------------------------------------------------------------------------------------- |
| `PENDING`   | The event was created and is waiting for Portal to deliver it (or to be retried by the automatic retry loop). |
| `DELIVERED` | Your alert webhook URL returned a `2XX` response on the latest attempt.                                       |
| `FAILED`    | Automatic retries were exhausted without a `2XX` response, or a manual retry failed.                          |
| `REPLAYING` | A manual retry is currently in flight (via the `/retries` endpoint).                                          |

The lifecycle looks like this:

```mermaid theme={null}
stateDiagram-v2
    [*] --> PENDING
    PENDING --> DELIVERED
    PENDING --> FAILED
    PENDING --> REPLAYING
    FAILED --> REPLAYING
    REPLAYING --> DELIVERED
    REPLAYING --> FAILED
    DELIVERED --> [*]
```

### Manual retries replace the automatic retry loop

The automatic retry handler only processes events whose status is `PENDING`. When you call the `/retries` endpoint on an event, Portal immediately flips its status to `REPLAYING` and enqueues a **single-shot** delivery attempt on a dedicated retry queue.

This has an important consequence:

<Warning>
  If you retry a delivery event that is still `PENDING`, the automatic retry loop will **stop** retrying it on its next attempt — because the event is no longer in the `PENDING` state. The manual retry becomes the only remaining attempt; whether it succeeds or fails, the event will move to `DELIVERED` or `FAILED` and stay there.
</Warning>

In practice this means:

* Retrying a `FAILED` event is safe and idempotent — there is no automatic retry running in parallel.
* Retrying a `PENDING` event short-circuits the remaining scheduled retries. Only do this when you want to force an immediate attempt instead of waiting for the next backoff window.
* Retrying a `REPLAYING` event is a no-op until the in-flight retry completes.
* Retrying a `DELIVERED` event will create another delivery attempt, but most integrations should treat already-delivered events as terminal.

## Types of alerts

### `Wallet Created`

Once you configure an alert webhook with `Wallet Created` selected as an event, Portal will send you a notification whenever one of your Portal clients successfully creates a wallet and stores its signing share. Portal makes a `POST` request to your configured alert webhook URL with a request body that contains the `clientId`, the `walletId`, the `signingSharePairId`, and the `curve` of the new wallet.

Each Portal client has a key for each of two curves (`SECP256K1` and `ED25519`), so you will receive one `Wallet Created` event per curve — two per client, each carrying that curve's `walletId` (the `clientId` is the same for both).

<Warning>
  **Retries:** When your webhook does not respond to a request, or if you do not respond with a `2XX` status code, Portal will retry the request up to 9 times with exponential backoff (see the [retry schedule](#retry-schedule)). If no successful response occurs after all retries, the webhook event will be marked `FAILED` and you can re-drive it with [`POST /custodians/me/alerts/webhooks/{alertWebhookId}/delivery-events/{deliveryEventId}/retries`](/api-reference/alert-webhooks/retry-an-alert-webhook-delivery-event). See [Delivery event lifecycle](#delivery-event-lifecycle) for details on how manual retries interact with the automatic retry loop.
</Warning>

**Headers:**

| Name               | Type   | Description                                                                                                                                                   |
| ------------------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Content-Type`     | String | `application/json`                                                                                                                                            |
| `X-WEBHOOK-SECRET` | String | You can find the alert webhook secret in the [Portal Admin Dashboard](https://app.portalhq.io) on the `"Webhooks"` page under the `"Alert Webhooks"` section. |

**Request body (examples):**

```json theme={null}
{
  "data": {
    "clientId": "clientId",
    "walletId": "walletId",
    "signingSharePairId": "signingSharePairId",
    "curve": "SECP256K1" // SECP256K1 or ED25519, or null if unavailable.
  },
  "metadata": {
    "custodianId": "custodianId",
    "environmentId": "environmentId",
  },
  "type": "WALLET_GENERATE_V1"
}
```

### `Wallet Backed Up`

Once you configure an alert webhook with `Wallet Backed Up` selected as an event, Portal will send you a notification whenever one of your Portal clients successfully backs up a wallet key. Portal makes a `POST` request to your configured alert webhook URL with a request body that contains the `clientId`, the `walletId`, the `curve`, the `backupMethod` used, and the `backupSharePairId` that was stored.

Each Portal client has a key for each of two curves (`SECP256K1` and `ED25519`), so you will receive one `Wallet Backed Up` event per curve — two per client, each carrying that curve's `walletId` (the `clientId` is the same for both). If a client backs up with more than one method, you will receive one event per method per curve.

<Warning>
  **Retries:** When your webhook does not respond to a request, or if you do not respond with a `2XX` status code, Portal will retry the request up to 9 times with exponential backoff (see the [retry schedule](#retry-schedule)). If no successful response occurs after all retries, the webhook event will be marked `FAILED` and you can re-drive it with [`POST /custodians/me/alerts/webhooks/{alertWebhookId}/delivery-events/{deliveryEventId}/retries`](/api-reference/alert-webhooks/retry-an-alert-webhook-delivery-event). See [Delivery event lifecycle](#delivery-event-lifecycle) for details on how manual retries interact with the automatic retry loop.
</Warning>

**Headers:**

| Name               | Type   | Description                                                                                                                                                   |
| ------------------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Content-Type`     | String | `application/json`                                                                                                                                            |
| `X-WEBHOOK-SECRET` | String | You can find the alert webhook secret in the [Portal Admin Dashboard](https://app.portalhq.io) on the `"Webhooks"` page under the `"Alert Webhooks"` section. |

**Request body (examples):**

```json theme={null}
{
  "data": {
    "clientId": "clientId",
    "walletId": "walletId",
    "curve": "SECP256K1", // SECP256K1 or ED25519, or null if unavailable.
    "backupMethod": "GDRIVE", // GDRIVE, ICLOUD, PASSWORD, PASSKEY, etc.
    "backupSharePairId": "backupSharePairId" // The backup share pair stored for this curve.
  },
  "metadata": {
    "custodianId": "custodianId",
    "environmentId": "environmentId",
  },
  "type": "WALLET_BACKUP_V1"
}
```

### `Wallet Recovered`

Once you configure an alert webhook with `Wallet Recovered` selected as an event, Portal will send you a notification whenever one of your Portal clients successfully recovers a wallet key and stores the recovered signing share. Portal makes a `POST` request to your configured alert webhook URL with a request body that contains the `clientId`, the `walletId`, the new `signingSharePairId`, and the `curve`.

Each Portal client has a key for each of two curves (`SECP256K1` and `ED25519`), so you will receive one `Wallet Recovered` event per curve — two per client, each carrying that curve's `walletId` (the `clientId` is the same for both).

<Warning>
  **Retries:** When your webhook does not respond to a request, or if you do not respond with a `2XX` status code, Portal will retry the request up to 9 times with exponential backoff (see the [retry schedule](#retry-schedule)). If no successful response occurs after all retries, the webhook event will be marked `FAILED` and you can re-drive it with [`POST /custodians/me/alerts/webhooks/{alertWebhookId}/delivery-events/{deliveryEventId}/retries`](/api-reference/alert-webhooks/retry-an-alert-webhook-delivery-event). See [Delivery event lifecycle](#delivery-event-lifecycle) for details on how manual retries interact with the automatic retry loop.
</Warning>

**Headers:**

| Name               | Type   | Description                                                                                                                                                   |
| ------------------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Content-Type`     | String | `application/json`                                                                                                                                            |
| `X-WEBHOOK-SECRET` | String | You can find the alert webhook secret in the [Portal Admin Dashboard](https://app.portalhq.io) on the `"Webhooks"` page under the `"Alert Webhooks"` section. |

**Request body (examples):**

```json theme={null}
{
  "data": {
    "clientId": "clientId",
    "walletId": "walletId",
    "signingSharePairId": "signingSharePairId",
    "curve": "SECP256K1" // SECP256K1 or ED25519, or null if unavailable.
  },
  "metadata": {
    "custodianId": "custodianId",
    "environmentId": "environmentId",
  },
  "type": "WALLET_RECOVER_V1"
}
```

### `Wallet Ejected`

Once you configure an alert webhook with `Wallet Ejected` selected as an event, Portal will send you notifications whenever one of your Portal clients ejects their wallet. When a wallet ejection occurs, Portal makes a `POST` request to your configured alert webhook URL with a request body that contains the `clientId` of the Portal client who ejected their wallet, as well as the `clientPlatform` and `clientPlatformVersion` of the client if available.

Ejecting affects a client's entire wallet across both curves at once, so Portal sends a single `Wallet Ejected` event per client — unlike the per-curve `Wallet Created`, `Wallet Backed Up`, and `Wallet Recovered` events above, which fire once per curve.

Regardless of your API response, the eject operation has already been processed for the Portal client who ejected their wallet.

<Warning>
  **Retries:** `Wallet Ejected` events are delivered with a single synchronous attempt and are not automatically retried (see the [retry schedule](#retry-schedule)). If no successful response occurs, the webhook event will be marked `FAILED` and you can re-drive it with [`POST /custodians/me/alerts/webhooks/{alertWebhookId}/delivery-events/{deliveryEventId}/retries`](/api-reference/alert-webhooks/retry-an-alert-webhook-delivery-event). See [Delivery event lifecycle](#delivery-event-lifecycle) for details on how manual retries interact with the automatic retry loop.
</Warning>

**Headers:**

| Name               | Type   | Description                                                                                                                                                   |
| ------------------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Content-Type`     | String | `application/json`                                                                                                                                            |
| `X-WEBHOOK-SECRET` | String | You can find the alert webhook secret in the [Portal Admin Dashboard](https://app.portalhq.io) on the `"Webhooks"` page under the `"Alert Webhooks"` section. |

**Request body (examples):**

```json theme={null}
{
  "data": {
    "clientId": "clientId",
    "clientPlatform": "NATIVE_IOS", // CLI, NATIVE_ANDROID, NATIVE_IOS, REACT_NATIVE, WEB, EMM, or null.
    "clientPlatformVersion": "1.0.0" // The version of the client platform, or null if not provided.
  },
  "metadata": {
    "custodianId": "custodianId",
    "environmentId": "environmentId",
  },
  "type": "WALLET_EJECT_V1"
}
```

### `Signature Approvals`

After enabling `Signature Approvals`, Portal will make a request to your configured alert webhook URL any time that one of your Portal wallets attempt to sign. This request will contain the `chainId`, `clientId`, and `signingRequest`, which you can use to derive if the signing request should be allowed to continue.

You must respond with a status code of `200-299` for the signing request to continue. To deny the request, you must respond with a `400` status code. **If any other status code is received, or if 30 seconds passes with no response from your API, we will deny the signing request.**

<Warning>
  If your API does not respond to `Signature Approval` alerts, your Portal clients' signing requests will be denied to ensure Portal only continues processing the signing request when your API gives explicit permission to do so.
</Warning>

<Warning>
  **Retries:** `Signature Approvals` events are delivered with a single synchronous attempt and are not automatically retried — the signing decision is made from that one response (see the [retry schedule](#retry-schedule)). If no successful response occurs, the webhook event will be marked `FAILED` and you can re-drive it with [`POST /custodians/me/alerts/webhooks/{alertWebhookId}/delivery-events/{deliveryEventId}/retries`](/api-reference/alert-webhooks/retry-an-alert-webhook-delivery-event). Note that a manual retry does not retroactively change the original signing decision. See [Delivery event lifecycle](#delivery-event-lifecycle) for details on how manual retries interact with the automatic retry loop.
</Warning>

**Headers:**

| Name               | Type   | Description                                                                                                                                                   |
| ------------------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Content-Type`     | String | `application/json`                                                                                                                                            |
| `X-WEBHOOK-SECRET` | String | You can find the alert webhook secret in the [Portal Admin Dashboard](https://app.portalhq.io) on the `"Webhooks"` page under the `"Alert Webhooks"` section. |

**Request body (examples):**

<Tabs>
  <Tab title="EVM">
    ```json theme={null}
    {
      "data": {
        "chainId": "eip155:11155111", // Ethereum Sepolia
        "clientId": "clientId",
        "signingRequest": {
          "method": "eth_sendTransaction",
          "params": "{\"from\":\"0xec445db8df2208dde9b5ad87e77b6a4d45855d4f\",\"to\":\"0xdFd8302f44727A6348F702fF7B594f127dE3A902\",\"value\":\"0x5af3107a4000\"}"
        }
      },
      "metadata": {
        "custodianId": "custodianId",
        "environmentId": "environmentId",
        "signatureApprovalMemo": "optional memo"
      },
      "type": "PRE_SIGN_V1"
    }
    ```
  </Tab>

  <Tab title="Solana">
    ```json theme={null}
    {
      "data": {
        "chainId": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", // Solana Devnet
        "clientId": "clientId",
        "signingRequest": {
          "method": "sol_signAndSendTransaction",
          "params": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAED4QZTgurPkrah5b0uP9SoRjT2+hCnv/1KSr7nRZWS8aJaUV/q8rCkYm5Cw38QFa6J0WVbD1/rY7eCTuE2Bz8jVgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgje1UaxreFr4adOu8Z+RYX073+qUrItkboInHuIhJsQBAgIAAQwCAAAAQEIPAAAAAAA=" // base64 serialized transaction
        }
      },
      "metadata": {
        "custodianId": "custodianId",
        "environmentId": "environmentId",
        "signatureApprovalMemo": "optional memo"
      },
      "type": "PRE_SIGN_V1"
    }
    ```
  </Tab>

  <Tab title="Tron">
    ```json theme={null}
    {
      "data": {
        "chainId": "tron:nile", // Tron Nile Testnet
        "clientId": "clientId",
        "signingRequest": {
          "method": "tron_sendTransaction",
          "params": "CgJIziIID1+8YyspzttA0JCXytIyWmYIARJiCi10eXBlLmdvb2dsZWFwaXMuY29tL3Byb3RvY29sLlRyYW5zZmVyQ29udHJhY3QSMQoVQWbePJYEuac1QStQiExU4/oKJzizEhVBAtvp5WmlMcd8vqwl2REpbKTUQ6EY6Adw8LuTytIy"
        }
      },
      "metadata": {
        "custodianId": "custodianId",
        "environmentId": "environmentId",
        "signatureApprovalMemo": "optional memo"
      },
      "type": "PRE_SIGN_V1"
    }
    ```
  </Tab>

  <Tab title="Stellar">
    ```json theme={null}
    {
      "data": {
        "chainId": "stellar:testnet", // Stellar Testnet
        "clientId": "clientId",
        "signingRequest": {
          "method": "stellar_sendTransaction",
          "params": "AAAAAgAAAADhBlOC6s+StqHlvS4/1KhGNPb6EKe//UpKvudFlZLxogAAAGQAEwrRAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAADNomLKKfoL22U8hEmltigZsRZo6o6YhQHvKPjsDTu5yAAAAAAAAAAAAAAnEAAAAAAAAAAA"
        }
      },
      "metadata": {
        "custodianId": "custodianId",
        "environmentId": "environmentId",
        "signatureApprovalMemo": "optional memo"
      },
      "type": "PRE_SIGN_V1"
    }
    ```
  </Tab>
</Tabs>

### `EVM Wallet Transactions`

Immediately after configuring the alert webhook with `EVM Wallet Transactions` selected as an event, Portal starts to listen for any inbound/outbound EIP-155 transactions, approvals, and revocations for your clients that have an EIP-155 address. From then on when you create a new client with an EIP-155 address, Portal will notify you of their on-chain transactions.

When an EVM transaction occurs, Portal makes a `POST` request to your configured alert webhook URL with a request body that contains the alert webhook event's details. You will receive 2 alerts per transaction — The first alert is `unconfirmed` and, once the transaction receives sufficient confirmations, the second alert is `confirmed`.

<Note>
  If both the sender and the receiver are addresses you've registered with Portal, you'll receive **two events per confirmation state** — one `OUTBOUND` and one `INBOUND` — for the same on-chain transfer. See [Dual events for internal transfers](#dual-events-for-internal-transfers) for the full explanation and example payloads.
</Note>

<Note>
  `EVM Wallet Transactions` currently sends alerts with the following use cases:

  1. Receive native tokens (ie. `ETH`) from another address
  2. Send native tokens (ie. `ETH`) to another address
  3. Receive non-native tokens (ie. `USDC`) from another address
  4. Send non-native tokens (ie. `USDC`) to another address
  5. Approve non-native tokens (ie. approve `USDC` for a spender)
  6. Revoke non-native token approvals (ie. set a spender's allowance to 0)
</Note>

<Note>
  When a block is unconfirmed, chain reorganizations may occur. If a reorganization happens, the original block's data is replaced with the updated block. This means you might receive an `unconfirmed` alert without a subsequent `confirmed` alert for the same transaction if this happens.
</Note>

<Warning>
  **Retries:** When your webhook does not respond to a request, or if you do not respond with a `2XX` status code, Portal will retry the request up to 9 times with exponential backoff (see the [retry schedule](#retry-schedule)). If no successful response occurs after all retries, the webhook event will be marked `FAILED` and you can re-drive it with [`POST /custodians/me/alerts/webhooks/{alertWebhookId}/delivery-events/{deliveryEventId}/retries`](/api-reference/alert-webhooks/retry-an-alert-webhook-delivery-event). See [Delivery event lifecycle](#delivery-event-lifecycle) for details on how manual retries interact with the automatic retry loop.
</Warning>

See the table below for all EVM chains we support for `EVM Wallet Transactions` alerts.

| Name             | Chain ID          | Blocks until confirmed |
| ---------------- | ----------------- | ---------------------- |
| Ethereum         | `eip155:1`        | `12`                   |
| Ethereum Sepolia | `eip155:11155111` | `18`                   |
| Polygon          | `eip155:137`      | `100`                  |
| Polygon Amoy     | `eip155:80002`    | `100`                  |
| Arbitrum         | `eip155:42161`    | `18`                   |
| Arbitrum Sepolia | `eip155:421614`   | `600`                  |
| Optimism         | `eip155:10`       | `500`                  |
| Optimism Sepolia | `eip155:11155420` | `600`                  |
| Base             | `eip155:8453`     | `100`                  |
| Base Sepolia     | `eip155:84532`    | `100`                  |

**Headers:**

| Name               | Type   | Description                                                                                                                                                   |
| ------------------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Content-Type`     | String | `application/json`                                                                                                                                            |
| `X-WEBHOOK-SECRET` | String | You can find the alert webhook secret in the [Portal Admin Dashboard](https://app.portalhq.io) on the `"Webhooks"` page under the `"Alert Webhooks"` section. |

**Request body (examples)**

<Tabs>
  <Tab title="Receive ETH">
    **Unconfirmed alert:**

    ```json theme={null}
    {
      "data": [
        {
          "to": "0xab44dca8a4c606298d6be3e1b53115ed4785fe1d", // Your Portal wallet
          "from": "0x76acbd1eb20236f303d6f5fc3d50fe5f88caec3f",
          "amount": "0.01",
          "chainId": "eip155:11155111",
          "assetType": "NATIVE_TOKEN",
          "chainName": "sepolia",
          "direction": "INBOUND",
          "tokenSymbol": "ETH",
          "metadata": {
            "fee": {
              "amount": "0.001933021424187",
              "decimals": 18,
              "rawAmount": "1933021424187000",
              "tokenSymbol": "ETH"
            },
            "sentAt": "2025-02-05T14:44:00.000Z",
            "confirmed": false,
            "actualGasCost": null,
            "formattedActualGasCost": null,
            "rawAmount": "10000000000000000",
            "tokenName": "ETH",
            "nftTokenId": null,
            "blockNumber": "7645517",
            "triggeredBy": "0xab44dca8a4c606298d6be3e1b53115ed4785fe1d",
            "tokenAddress": null,
            "tokenDecimals": 18,
            "approvalSpender": null,
            "transactionHash": "0xd3e43c0c352d6d60e16115416ae9d1406394e149096d449a7c88068f923ada8a",
            "userOperationHash": null
          }
        }
      ],
      "metadata": {
        "custodianId": "custodianId",
        "environmentId": "environmentId",
        "rawWebhookPayload": [],
      },
      "type": "EIP_155_TX_V1"
    }
    ```

    **Confirmed alert:**

    ```json theme={null}
    {
      "data": [
        {
          "to": "0xab44dca8a4c606298d6be3e1b53115ed4785fe1d", // Your Portal wallet
          "from": "0x76acbd1eb20236f303d6f5fc3d50fe5f88caec3f",
          "amount": "0.01",
          "chainId": "eip155:11155111",
          "assetType": "NATIVE_TOKEN",
          "chainName": "sepolia",
          "direction": "INBOUND",
          "tokenSymbol": "ETH",
          "metadata": {
            "fee": {
              "amount": "0.001933021424187",
              "decimals": 18,
              "rawAmount": "1933021424187000",
              "tokenSymbol": "ETH"
            },
            "sentAt": "2025-02-05T14:44:00.000Z",
            "confirmed": true,
            "actualGasCost": null,
            "formattedActualGasCost": null,
            "rawAmount": "10000000000000000",
            "tokenName": "ETH",
            "nftTokenId": null,
            "blockNumber": "7645517",
            "triggeredBy": "0xab44dca8a4c606298d6be3e1b53115ed4785fe1d",
            "tokenAddress": null,
            "tokenDecimals": 18,
            "approvalSpender": null,
            "transactionHash": "0xd3e43c0c352d6d60e16115416ae9d1406394e149096d449a7c88068f923ada8a",
            "userOperationHash": null
          }
        }
      ],
      "metadata": {
        "custodianId": "custodianId",
        "environmentId": "environmentId",
        "rawWebhookPayload": []
      },
      "type": "EIP_155_TX_V1"
    }
    ```
  </Tab>

  <Tab title="Send ETH">
    **Unconfirmed alert:**

    ```json theme={null}
    {
      "data": [
        {
          "to": "0xdfd8302f44727a6348f702ff7b594f127de3a902",
          "from": "0xab44dca8a4c606298d6be3e1b53115ed4785fe1d", // Your Portal wallet
          "amount": "0.001",
          "chainId": "eip155:11155111",
          "assetType": "NATIVE_TOKEN",
          "chainName": "sepolia",
          "direction": "OUTBOUND",
          "tokenSymbol": "ETH",
          "metadata": {
            "fee": {
              "amount": "0.001955140746657",
              "decimals": 18,
              "rawAmount": "1955140746657000",
              "tokenSymbol": "ETH"
            },
            "sentAt": "2025-02-05T14:52:24.000Z",
            "confirmed": false,
            "actualGasCost": null,
            "formattedActualGasCost": null,
            "rawAmount": "1000000000000000",
            "tokenName": "ETH",
            "nftTokenId": null,
            "blockNumber": "7645557",
            "triggeredBy": "0xab44dca8a4c606298d6be3e1b53115ed4785fe1d",
            "tokenAddress": null,
            "tokenDecimals": 18,
            "approvalSpender": null,
            "transactionHash": "0x297602294abe88d847a0e74af600b149cf3ed31f4cd775b37628cead9b2fabb6",
            "userOperationHash": null
          }
        }
      ],
      "metadata": {
        "custodianId": "custodianId",
        "environmentId": "environmentId"
      },
      "type": "EIP_155_TX_V1"
    }
    ```

    **Confirmed alert:**

    ```json theme={null}
    {
      "data": [
        {
          "to": "0xdfd8302f44727a6348f702ff7b594f127de3a902",
          "from": "0xab44dca8a4c606298d6be3e1b53115ed4785fe1d", // Your Portal wallet
          "amount": "0.001",
          "chainId": "eip155:11155111",
          "assetType": "NATIVE_TOKEN",
          "chainName": "sepolia",
          "direction": "OUTBOUND",
          "tokenSymbol": "ETH",
          "metadata": {
            "fee": {
              "amount": "0.001955140746657",
              "decimals": 18,
              "rawAmount": "1955140746657000",
              "tokenSymbol": "ETH"
            },
            "sentAt": "2025-02-05T14:52:24.000Z",
            "confirmed": true,
            "actualGasCost": null,
            "formattedActualGasCost": null,
            "rawAmount": "1000000000000000",
            "tokenName": "ETH",
            "nftTokenId": null,
            "blockNumber": "7645557",
            "triggeredBy": "0xab44dca8a4c606298d6be3e1b53115ed4785fe1d",
            "tokenAddress": null,
            "tokenDecimals": 18,
            "approvalSpender": null,
            "transactionHash": "0x297602294abe88d847a0e74af600b149cf3ed31f4cd775b37628cead9b2fabb6",
            "userOperationHash": null
          }
        }
      ],
      "metadata": {
        "custodianId": "custodianId",
        "environmentId": "environmentId"
      },
      "type": "EIP_155_TX_V1"
    }
    ```
  </Tab>

  <Tab title="Receive USDT">
    **Unconfirmed alert:**

    ```json theme={null}
    {
      "data": [
        {
          "to": "0xab44dca8a4c606298d6be3e1b53115ed4785fe1d", // Your Portal wallet
          "from": "0xdfd8302f44727a6348f702ff7b594f127de3a902",
          "amount": "2",
          "chainId": "eip155:11155111",
          "assetType": "NON_NATIVE_TOKEN",
          "chainName": "sepolia",
          "direction": "INBOUND",
          "tokenSymbol": "USDT",
          "metadata": {
            "fee": {
              "amount": "0.003163263549129086",
              "decimals": 18,
              "rawAmount": "3163263549129086",
              "tokenSymbol": "ETH"
            },
            "sentAt": "2025-02-05T16:50:12.000Z",
            "confirmed": false,
            "actualGasCost": null,
            "formattedActualGasCost": null,
            "rawAmount": "2000000",
            "tokenName": "USDT",
            "nftTokenId": null,
            "blockNumber": "7646117",
            "triggeredBy": "0xab44dca8a4c606298d6be3e1b53115ed4785fe1d",
            "tokenAddress": "0x419fe9f14ff3aa22e46ff1d03a73edf3b70a62ed",
            "tokenDecimals": "6",
            "approvalSpender": null,
            "transactionHash": "0x165f68557bb15ca9396b58f9f71f5113b83affce4fe445b37728af6c3fdb3e48",
            "userOperationHash": null
          }
        }
      ],
      "metadata": {
        "custodianId": "custodianId",
        "environmentId": "environmentId"
      },
      "type": "EIP_155_TX_V1"
    }
    ```

    **Confirmed alert:**

    ```json theme={null}
    {
      "data": [
        {
          "to": "0xab44dca8a4c606298d6be3e1b53115ed4785fe1d", // Your Portal wallet
          "from": "0xdfd8302f44727a6348f702ff7b594f127de3a902",
          "amount": "2",
          "chainId": "eip155:11155111",
          "assetType": "NON_NATIVE_TOKEN",
          "chainName": "sepolia",
          "direction": "INBOUND",
          "tokenSymbol": "USDT",
          "metadata": {
            "fee": {
              "amount": "0.003163263549129086",
              "decimals": 18,
              "rawAmount": "3163263549129086",
              "tokenSymbol": "ETH"
            },
            "sentAt": "2025-02-05T16:50:12.000Z",
            "confirmed": true,
            "actualGasCost": null,
            "formattedActualGasCost": null,
            "rawAmount": "2000000",
            "tokenName": "USDT",
            "nftTokenId": null,
            "blockNumber": "7646117",
            "triggeredBy": "0xab44dca8a4c606298d6be3e1b53115ed4785fe1d",
            "tokenAddress": "0x419fe9f14ff3aa22e46ff1d03a73edf3b70a62ed",
            "tokenDecimals": "6",
            "approvalSpender": null,
            "transactionHash": "0x165f68557bb15ca9396b58f9f71f5113b83affce4fe445b37728af6c3fdb3e48",
            "userOperationHash": null
          }
        }
      ],
      "metadata": {
        "custodianId": "custodianId",
        "environmentId": "environmentId"
      },
      "type": "EIP_155_TX_V1"
    }
    ```
  </Tab>

  <Tab title="Send USDT">
    **Unconfirmed alert:**

    ```json theme={null}
    {
      "data": [
        {
          "to": "0xdfd8302f44727a6348f702ff7b594f127de3a902",
          "from": "0xab44dca8a4c606298d6be3e1b53115ed4785fe1d", // Your Portal wallet
          "amount": "0.1",
          "chainId": "eip155:11155111",
          "assetType": "NON_NATIVE_TOKEN",
          "chainName": "sepolia",
          "direction": "OUTBOUND",
          "tokenSymbol": "USDT",
          "metadata": {
            "fee": {
              "amount": "0.002395433635667721",
              "decimals": 18,
              "rawAmount": "2395433635667721",
              "tokenSymbol": "ETH"
            },
            "sentAt": "2025-02-05T16:58:36.000Z",
            "confirmed": false,
            "actualGasCost": null,
            "formattedActualGasCost": null,
            "rawAmount": "100000",
            "tokenName": "USDT",
            "nftTokenId": null,
            "blockNumber": "7646155",
            "triggeredBy": "0xab44dca8a4c606298d6be3e1b53115ed4785fe1d",
            "tokenAddress": "0x419fe9f14ff3aa22e46ff1d03a73edf3b70a62ed",
            "tokenDecimals": "6",
            "approvalSpender": null,
            "transactionHash": "0xa1da1738225b012c328724454dd46db4279133cfbf4a3054c094f3986de9ffe0",
            "userOperationHash": null
          }
        }
      ],
      "metadata": {
        "custodianId": "custodianId",
        "environmentId": "environmentId"
      },
      "type": "EIP_155_TX_V1"
    }
    ```

    **Confirmed alert:**

    ```json theme={null}
    {
      "data": [
        {
          "to": "0xdfd8302f44727a6348f702ff7b594f127de3a902",
          "from": "0xab44dca8a4c606298d6be3e1b53115ed4785fe1d", // Your Portal wallet
          "amount": "0.1",
          "chainId": "eip155:11155111",
          "assetType": "NON_NATIVE_TOKEN",
          "chainName": "sepolia",
          "direction": "OUTBOUND",
          "tokenSymbol": "USDT",
          "metadata": {
            "fee": {
              "amount": "0.002395433635667721",
              "decimals": 18,
              "rawAmount": "2395433635667721",
              "tokenSymbol": "ETH"
            },
            "sentAt": "2025-02-05T16:58:36.000Z",
            "confirmed": true,
            "actualGasCost": null,
            "formattedActualGasCost": null,
            "rawAmount": "100000",
            "tokenName": "USDT",
            "nftTokenId": null,
            "blockNumber": "7646155",
            "triggeredBy": "0xab44dca8a4c606298d6be3e1b53115ed4785fe1d",
            "tokenAddress": "0x419fe9f14ff3aa22e46ff1d03a73edf3b70a62ed",
            "tokenDecimals": "6",
            "approvalSpender": null,
            "transactionHash": "0xa1da1738225b012c328724454dd46db4279133cfbf4a3054c094f3986de9ffe0",
            "userOperationHash": null
          }
        }
      ],
      "metadata": {
        "custodianId": "custodianId",
        "environmentId": "environmentId"
      },
      "type": "EIP_155_TX_V1"
    }
    ```
  </Tab>

  <Tab title="Approve token">
    **Unconfirmed alert:**

    ```json theme={null}
    {
      "data": [
        {
          "to": "0xee5ca6275c9cfaec5ad42b40ebdbe6073fbcc4a5",
          "from": "0x8cd6145d0555b867a708ad92f311bc6d67d41fd2", // Your Portal wallet
          "amount": "0.12345",
          "chainId": "eip155:11155111",
          "metadata": {
            "fee": {
              "amount": "0.000472352150647304",
              "decimals": 18,
              "rawAmount": "472352150647304",
              "tokenSymbol": "ETH"
            },
            "sentAt": "2026-02-10T18:30:12.000Z",
            "confirmed": false,
            "actualGasCost": "0x39fb8298c6e800",
            "formattedActualGasCost": "16320612000000000",
            "rawAmount": "123450",
            "tokenName": "USDC",
            "nftTokenId": null,
            "blockNumber": "10233184",
            "triggeredBy": "0x8cd6145d0555b867a708ad92f311bc6d67d41fd2",
            "tokenAddress": "0x1c7d4b196cb0c7b01d743fbc6116a902379c7238",
            "tokenDecimals": "6",
            "approvalSpender": "0xee5ca6275c9cfaec5ad42b40ebdbe6073fbcc4a5",
            "transactionHash": "0x39537e4f9a06d0356ea2c201190ff9fd2ffa4c1220c5b3eca7e15b1c03f64273",
            "userOperationHash": "0x13b97495af27950461811eb68667d3ca631f9a510d5fe6b422d964962fbe995a"
          },
          "assetType": "ERC20_APPROVAL",
          "chainName": "sepolia",
          "direction": "OUTBOUND",
          "tokenSymbol": "USDC"
        }
      ],
      "metadata": {
        "txs": [
          {
            "r": "39239007254912289268771055931432113831270460789053050427072713407646027577443",
            "s": "26623931638950271666893416961845058711739468843419989769967475848834369193733",
            "v": "0",
            "gas": "446711",
            "hash": "0x39537e4f9a06d0356ea2c201190ff9fd2ffa4c1220c5b3eca7e15b1c03f64273",
            "type": "2",
            "input": "0x1fad948c00000000000000000000000000000000000000000000000000000000000000400000000000000000000000004337016838785634c63fce393bfc6222564436c4000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000008cd6145d0555b867a708ad92f311bc6d67d41fd200000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000200de000000000000000000000000000000000000000000000000000000000001e41f000000000000000000000000000000000000000000000000000000000000d33c0000000000000000000000000000000000000000000000000000000054c87c2e0000000000000000000000000000000000000000000000000000000000195d3600000000000000000000000000000000000000000000000000000000000002c0000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104519454470000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902379c72380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044095ea7b3000000000000000000000000ee5ca6275c9cfaec5ad42b40ebdbe6073fbcc4a5000000000000000000000000000000000000000000000000000000000001e23a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000626666666666667849c56f2850848ce1c4da65c68b010000698b7b7f000000000000053dd9ed70c3f6ad900618ff48c6c00d5602d43378f8ca53f57e962c7a74e3cd38ecd4efa48743a0178be82451503ad6eadac99a6bab178321046537e6ef1a7a1c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004500000000295dde855329661f3fbb2f2e84a269102acc5211aa255c3007e60368f2bc13b479bf98ea586f92485e23a404d125421d2ae2fda087337471c2e889535c6262bd1b000000000000000000000000000000000000000000000000000000",
            "nonce": "58551",
            "value": "0",
            "gasPrice": "1057399864",
            "toAddress": "0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789",
            "fromAddress": "0x4337016838785634c63fce393bfc6222564436c4",
            "receiptRoot": null,
            "triggered_by": ["0x8cd6145d0555b867a708ad92f311bc6d67d41fd2"],
            "receiptStatus": "1",
            "receiptGasUsed": "153229",
            "transactionFee": "0.000162024323760856",
            "transactionIndex": "97",
            "receiptContractAddress": null,
            "receiptCumulativeGasUsed": "19941570"
          }
        ],
        "logs": [
          {
            "data": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
            "topic0": "0x7a270f29ae17e8e2304ff1245deb50c3b6206bca82928d904f3e284d35c5ffd2",
            "topic1": "0x13b97495af27950461811eb68667d3ca631f9a510d5fe6b422d964962fbe995a",
            "topic2": "0x0000000000000000000000008cd6145d0555b867a708ad92f311bc6d67d41fd2",
            "topic3": null,
            "address": "0x6666666666667849c56f2850848ce1c4da65c68b",
            "logIndex": "193",
            "triggered_by": ["0x8cd6145d0555b867a708ad92f311bc6d67d41fd2"],
            "transactionHash": "0x39537e4f9a06d0356ea2c201190ff9fd2ffa4c1220c5b3eca7e15b1c03f64273"
          },
          {
            "data": "0x000000000000000000000000000000000000000000000000000000000001e23a",
            "topic0": "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925",
            "topic1": "0x0000000000000000000000008cd6145d0555b867a708ad92f311bc6d67d41fd2",
            "topic2": "0x000000000000000000000000ee5ca6275c9cfaec5ad42b40ebdbe6073fbcc4a5",
            "topic3": null,
            "address": "0x1c7d4b196cb0c7b01d743fbc6116a902379c7238",
            "logIndex": "195",
            "triggered_by": [
              "0x8cd6145d0555b867a708ad92f311bc6d67d41fd2",
              "0xee5ca6275c9cfaec5ad42b40ebdbe6073fbcc4a5"
            ],
            "transactionHash": "0x39537e4f9a06d0356ea2c201190ff9fd2ffa4c1220c5b3eca7e15b1c03f64273"
          },
          {
            "data": "0x00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000098b979dbd6590000000000000000000000000000000000000000000000000000000000026c4b",
            "topic0": "0x49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f",
            "topic1": "0x13b97495af27950461811eb68667d3ca631f9a510d5fe6b422d964962fbe995a",
            "topic2": "0x0000000000000000000000008cd6145d0555b867a708ad92f311bc6d67d41fd2",
            "topic3": "0x0000000000000000000000006666666666667849c56f2850848ce1c4da65c68b",
            "address": "0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789",
            "logIndex": "196",
            "triggered_by": ["0x8cd6145d0555b867a708ad92f311bc6d67d41fd2"],
            "transactionHash": "0x39537e4f9a06d0356ea2c201190ff9fd2ffa4c1220c5b3eca7e15b1c03f64273"
          }
        ],
        "block": {
          "hash": "0x90e7bfd7100ae7858028e870efd6d195814ac2cb0ccadf88ebc3e901e053c77e",
          "number": "10233184",
          "timestamp": "1770748212"
        },
        "chainId": "eip155:11155111",
        "retries": 0,
        "confirmed": false,
        "custodianId": "cl7hygkd200063g6l5nn0",
        "environmentId": "8c95033b-f3f0-4a15-a4ae-8938e3087990"
      },
      "type": "EIP_155_TX_V1"
    }
    ```

    **Confirmed alert:**

    ```json theme={null}
    {
      "data": [
        {
          "to": "0xee5ca6275c9cfaec5ad42b40ebdbe6073fbcc4a5",
          "from": "0x8cd6145d0555b867a708ad92f311bc6d67d41fd2", // Your Portal wallet
          "amount": "0.12345",
          "chainId": "eip155:11155111",
          "metadata": {
            "fee": {
              "amount": "0.000472352150647304",
              "decimals": 18,
              "rawAmount": "472352150647304",
              "tokenSymbol": "ETH"
            },
            "sentAt": "2026-02-10T18:30:12.000Z",
            "confirmed": true,
            "actualGasCost": "0x39fb8298c6e800",
            "formattedActualGasCost": "16320612000000000",
            "rawAmount": "123450",
            "tokenName": "USDC",
            "nftTokenId": null,
            "blockNumber": "10233184",
            "triggeredBy": "0x8cd6145d0555b867a708ad92f311bc6d67d41fd2",
            "tokenAddress": "0x1c7d4b196cb0c7b01d743fbc6116a902379c7238",
            "tokenDecimals": "6",
            "approvalSpender": "0xee5ca6275c9cfaec5ad42b40ebdbe6073fbcc4a5",
            "transactionHash": "0x39537e4f9a06d0356ea2c201190ff9fd2ffa4c1220c5b3eca7e15b1c03f64273",
            "userOperationHash": "0x13b97495af27950461811eb68667d3ca631f9a510d5fe6b422d964962fbe995a"
          },
          "assetType": "ERC20_APPROVAL",
          "chainName": "sepolia",
          "direction": "OUTBOUND",
          "tokenSymbol": "USDC"
        }
      ],
      "metadata": {
        "txs": [
          {
            "r": "39239007254912289268771055931432113831270460789053050427072713407646027577443",
            "s": "26623931638950271666893416961845058711739468843419989769967475848834369193733",
            "v": "0",
            "gas": "446711",
            "hash": "0x39537e4f9a06d0356ea2c201190ff9fd2ffa4c1220c5b3eca7e15b1c03f64273",
            "type": "2",
            "input": "0x1fad948c00000000000000000000000000000000000000000000000000000000000000400000000000000000000000004337016838785634c63fce393bfc6222564436c4000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000008cd6145d0555b867a708ad92f311bc6d67d41fd200000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000200de000000000000000000000000000000000000000000000000000000000001e41f000000000000000000000000000000000000000000000000000000000000d33c0000000000000000000000000000000000000000000000000000000054c87c2e0000000000000000000000000000000000000000000000000000000000195d3600000000000000000000000000000000000000000000000000000000000002c0000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104519454470000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902379c72380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044095ea7b3000000000000000000000000ee5ca6275c9cfaec5ad42b40ebdbe6073fbcc4a5000000000000000000000000000000000000000000000000000000000001e23a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000626666666666667849c56f2850848ce1c4da65c68b010000698b7b7f000000000000053dd9ed70c3f6ad900618ff48c6c00d5602d43378f8ca53f57e962c7a74e3cd38ecd4efa48743a0178be82451503ad6eadac99a6bab178321046537e6ef1a7a1c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004500000000295dde855329661f3fbb2f2e84a269102acc5211aa255c3007e60368f2bc13b479bf98ea586f92485e23a404d125421d2ae2fda087337471c2e889535c6262bd1b000000000000000000000000000000000000000000000000000000",
            "nonce": "58551",
            "value": "0",
            "gasPrice": "1057399864",
            "toAddress": "0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789",
            "fromAddress": "0x4337016838785634c63fce393bfc6222564436c4",
            "receiptRoot": null,
            "triggered_by": ["0x8cd6145d0555b867a708ad92f311bc6d67d41fd2"],
            "receiptStatus": "1",
            "receiptGasUsed": "153229",
            "transactionFee": "0.000162024323760856",
            "transactionIndex": "97",
            "receiptContractAddress": null,
            "receiptCumulativeGasUsed": "19941570"
          }
        ],
        "logs": [
          {
            "data": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
            "topic0": "0x7a270f29ae17e8e2304ff1245deb50c3b6206bca82928d904f3e284d35c5ffd2",
            "topic1": "0x13b97495af27950461811eb68667d3ca631f9a510d5fe6b422d964962fbe995a",
            "topic2": "0x0000000000000000000000008cd6145d0555b867a708ad92f311bc6d67d41fd2",
            "topic3": null,
            "address": "0x6666666666667849c56f2850848ce1c4da65c68b",
            "logIndex": "193",
            "triggered_by": ["0x8cd6145d0555b867a708ad92f311bc6d67d41fd2"],
            "transactionHash": "0x39537e4f9a06d0356ea2c201190ff9fd2ffa4c1220c5b3eca7e15b1c03f64273"
          },
          {
            "data": "0x000000000000000000000000000000000000000000000000000000000001e23a",
            "topic0": "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925",
            "topic1": "0x0000000000000000000000008cd6145d0555b867a708ad92f311bc6d67d41fd2",
            "topic2": "0x000000000000000000000000ee5ca6275c9cfaec5ad42b40ebdbe6073fbcc4a5",
            "topic3": null,
            "address": "0x1c7d4b196cb0c7b01d743fbc6116a902379c7238",
            "logIndex": "195",
            "triggered_by": [
              "0x8cd6145d0555b867a708ad92f311bc6d67d41fd2",
              "0xee5ca6275c9cfaec5ad42b40ebdbe6073fbcc4a5"
            ],
            "transactionHash": "0x39537e4f9a06d0356ea2c201190ff9fd2ffa4c1220c5b3eca7e15b1c03f64273"
          },
          {
            "data": "0x00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000098b979dbd6590000000000000000000000000000000000000000000000000000000000026c4b",
            "topic0": "0x49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f",
            "topic1": "0x13b97495af27950461811eb68667d3ca631f9a510d5fe6b422d964962fbe995a",
            "topic2": "0x0000000000000000000000008cd6145d0555b867a708ad92f311bc6d67d41fd2",
            "topic3": "0x0000000000000000000000006666666666667849c56f2850848ce1c4da65c68b",
            "address": "0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789",
            "logIndex": "196",
            "triggered_by": ["0x8cd6145d0555b867a708ad92f311bc6d67d41fd2"],
            "transactionHash": "0x39537e4f9a06d0356ea2c201190ff9fd2ffa4c1220c5b3eca7e15b1c03f64273"
          }
        ],
        "block": {
          "hash": "0x90e7bfd7100ae7858028e870efd6d195814ac2cb0ccadf88ebc3e901e053c77e",
          "number": "10233184",
          "timestamp": "1770748212"
        },
        "chainId": "eip155:11155111",
        "retries": 0,
        "confirmed": true,
        "custodianId": "cl7hygkd200063g6l5nn0",
        "environmentId": "8c95033b-f3f0-4a15-a4ae-8938e3087990"
      },
      "type": "EIP_155_TX_V1"
    }
    ```
  </Tab>

  <Tab title="Revoke approval">
    **Unconfirmed alert:**

    ```json theme={null}
    {
      "data": [
        {
          "to": "0xee5ca6275c9cfaec5ad42b40ebdbe6073fbcc4a5",
          "from": "0x8cd6145d0555b867a708ad92f311bc6d67d41fd2", // Your Portal wallet
          "amount": "0",
          "chainId": "eip155:11155111",
          "metadata": {
            "fee": {
              "amount": "0.000445696678391486",
              "decimals": 18,
              "rawAmount": "445696678391486",
              "tokenSymbol": "ETH"
            },
            "sentAt": "2026-02-10T18:37:12.000Z",
            "confirmed": false,
            "actualGasCost": "0x39fb8298c6e800",
            "formattedActualGasCost": "16320612000000000",
            "rawAmount": "0",
            "tokenName": "USDC",
            "nftTokenId": null,
            "blockNumber": "10233219",
            "triggeredBy": "0x8cd6145d0555b867a708ad92f311bc6d67d41fd2",
            "tokenAddress": "0x1c7d4b196cb0c7b01d743fbc6116a902379c7238",
            "tokenDecimals": "6",
            "approvalSpender": "0xee5ca6275c9cfaec5ad42b40ebdbe6073fbcc4a5",
            "transactionHash": "0x433aade80380542c5f7294ba4eb43d08bb09352a605a1e41eeab88a823d4bb95",
            "userOperationHash": "0xf55e82b32f6d94a13607882f976a45834f2555faa871b1ea93c74ddc50857ede"
          },
          "assetType": "ERC20_APPROVAL",
          "chainName": "sepolia",
          "direction": "OUTBOUND",
          "tokenSymbol": "USDC"
        }
      ],
      "metadata": {
        "txs": [
          {
            "r": "69800324002112404822417539847516648270674922755017207604406921620605970296563",
            "s": "4822640581286792349111363455420646915276302091523407157768067240978332247031",
            "v": "1",
            "gas": "420689",
            "hash": "0x433aade80380542c5f7294ba4eb43d08bb09352a605a1e41eeab88a823d4bb95",
            "type": "2",
            "input": "0x1fad948c00000000000000000000000000000000000000000000000000000000000000400000000000000000000000004337013ef5cd421cef649fc58c1b0defd37bbeb1000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000008cd6145d0555b867a708ad92f311bc6d67d41fd2000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000001a396000000000000000000000000000000000000000000000000000000000001e41f000000000000000000000000000000000000000000000000000000000000d3140000000000000000000000000000000000000000000000000000000050bfe2440000000000000000000000000000000000000000000000000000000000195d3600000000000000000000000000000000000000000000000000000000000002c0000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104519454470000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902379c72380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044095ea7b3000000000000000000000000ee5ca6275c9cfaec5ad42b40ebdbe6073fbcc4a50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000626666666666667849c56f2850848ce1c4da65c68b010000698b7d21000000000000d40df9ba9d664ab5a8ff593573c305b43d64e873527d5cf6ce67dc48f99c16fa7f20287cc8c6c962d52a148fcafc96ec8b40606fb5a07c9da959e9f35207a5fe1c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004500000000bb5aaeb821928ef77ebdc0fe2e8c78fcec7e77cb7f7ab4892c33308605436aea164785e076475f85cbc15d70d937422a12d9a4735d9714ffde7e41fd1776e2321c000000000000000000000000000000000000000000000000000000",
            "nonce": "58458",
            "value": "0",
            "gasPrice": "1059444574",
            "toAddress": "0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789",
            "fromAddress": "0x4337013ef5cd421cef649fc58c1b0defd37bbeb1",
            "receiptRoot": null,
            "triggered_by": ["0x8cd6145d0555b867a708ad92f311bc6d67d41fd2"],
            "receiptStatus": "1",
            "receiptGasUsed": "133305",
            "transactionFee": "0.000141229258937070",
            "transactionIndex": "96",
            "receiptContractAddress": null,
            "receiptCumulativeGasUsed": "11334942"
          }
        ],
        "logs": [
          {
            "data": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
            "topic0": "0x7a270f29ae17e8e2304ff1245deb50c3b6206bca82928d904f3e284d35c5ffd2",
            "topic1": "0xf55e82b32f6d94a13607882f976a45834f2555faa871b1ea93c74ddc50857ede",
            "topic2": "0x0000000000000000000000008cd6145d0555b867a708ad92f311bc6d67d41fd2",
            "topic3": null,
            "address": "0x6666666666667849c56f2850848ce1c4da65c68b",
            "logIndex": "161",
            "triggered_by": ["0x8cd6145d0555b867a708ad92f311bc6d67d41fd2"],
            "transactionHash": "0x433aade80380542c5f7294ba4eb43d08bb09352a605a1e41eeab88a823d4bb95"
          },
          {
            "data": "0x0000000000000000000000000000000000000000000000000000000000000000",
            "topic0": "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925",
            "topic1": "0x0000000000000000000000008cd6145d0555b867a708ad92f311bc6d67d41fd2",
            "topic2": "0x000000000000000000000000ee5ca6275c9cfaec5ad42b40ebdbe6073fbcc4a5",
            "topic3": null,
            "address": "0x1c7d4b196cb0c7b01d743fbc6116a902379c7238",
            "logIndex": "163",
            "triggered_by": [
              "0x8cd6145d0555b867a708ad92f311bc6d67d41fd2",
              "0xee5ca6275c9cfaec5ad42b40ebdbe6073fbcc4a5"
            ],
            "transactionHash": "0x433aade80380542c5f7294ba4eb43d08bb09352a605a1e41eeab88a823d4bb95"
          },
          {
            "data": "0x00000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000085ce15510c570000000000000000000000000000000000000000000000000000000000021e67",
            "topic0": "0x49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f",
            "topic1": "0xf55e82b32f6d94a13607882f976a45834f2555faa871b1ea93c74ddc50857ede",
            "topic2": "0x0000000000000000000000008cd6145d0555b867a708ad92f311bc6d67d41fd2",
            "topic3": "0x0000000000000000000000006666666666667849c56f2850848ce1c4da65c68b",
            "address": "0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789",
            "logIndex": "164",
            "triggered_by": ["0x8cd6145d0555b867a708ad92f311bc6d67d41fd2"],
            "transactionHash": "0x433aade80380542c5f7294ba4eb43d08bb09352a605a1e41eeab88a823d4bb95"
          }
        ],
        "block": {
          "hash": "0x2778642f3ff6ae0dd81b8fa81185ab4f6bd7f7b28197eaba1593808cac0cd985",
          "number": "10233219",
          "timestamp": "1770748632"
        },
        "chainId": "eip155:11155111",
        "retries": 0,
        "confirmed": false,
        "custodianId": "cl7hygkd200063g6l5nn0",
        "environmentId": "8c95033b-f3f0-4a15-a4ae-8938e3087990"
      },
      "type": "EIP_155_TX_V1"
    }
    ```

    **Confirmed alert:**

    ```json theme={null}
    {
      "data": [
        {
          "to": "0xee5ca6275c9cfaec5ad42b40ebdbe6073fbcc4a5",
          "from": "0x8cd6145d0555b867a708ad92f311bc6d67d41fd2", // Your Portal wallet
          "amount": "0",
          "chainId": "eip155:11155111",
          "metadata": {
            "fee": {
              "amount": "0.000445696678391486",
              "decimals": 18,
              "rawAmount": "445696678391486",
              "tokenSymbol": "ETH"
            },
            "sentAt": "2026-02-10T18:37:12.000Z",
            "confirmed": true,
            "actualGasCost": "0x39fb8298c6e800",
            "formattedActualGasCost": "16320612000000000",
            "rawAmount": "0",
            "tokenName": "USDC",
            "nftTokenId": null,
            "blockNumber": "10233219",
            "triggeredBy": "0x8cd6145d0555b867a708ad92f311bc6d67d41fd2",
            "tokenAddress": "0x1c7d4b196cb0c7b01d743fbc6116a902379c7238",
            "tokenDecimals": "6",
            "approvalSpender": "0xee5ca6275c9cfaec5ad42b40ebdbe6073fbcc4a5",
            "transactionHash": "0x433aade80380542c5f7294ba4eb43d08bb09352a605a1e41eeab88a823d4bb95",
            "userOperationHash": "0xf55e82b32f6d94a13607882f976a45834f2555faa871b1ea93c74ddc50857ede"
          },
          "assetType": "ERC20_APPROVAL",
          "chainName": "sepolia",
          "direction": "OUTBOUND",
          "tokenSymbol": "USDC"
        }
      ],
      "metadata": {
        "txs": [
          {
            "r": "69800324002112404822417539847516648270674922755017207604406921620605970296563",
            "s": "4822640581286792349111363455420646915276302091523407157768067240978332247031",
            "v": "1",
            "gas": "420689",
            "hash": "0x433aade80380542c5f7294ba4eb43d08bb09352a605a1e41eeab88a823d4bb95",
            "type": "2",
            "input": "0x1fad948c00000000000000000000000000000000000000000000000000000000000000400000000000000000000000004337013ef5cd421cef649fc58c1b0defd37bbeb1000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000008cd6145d0555b867a708ad92f311bc6d67d41fd2000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000001a396000000000000000000000000000000000000000000000000000000000001e41f000000000000000000000000000000000000000000000000000000000000d3140000000000000000000000000000000000000000000000000000000050bfe2440000000000000000000000000000000000000000000000000000000000195d3600000000000000000000000000000000000000000000000000000000000002c0000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104519454470000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902379c72380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044095ea7b3000000000000000000000000ee5ca6275c9cfaec5ad42b40ebdbe6073fbcc4a50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000626666666666667849c56f2850848ce1c4da65c68b010000698b7d21000000000000d40df9ba9d664ab5a8ff593573c305b43d64e873527d5cf6ce67dc48f99c16fa7f20287cc8c6c962d52a148fcafc96ec8b40606fb5a07c9da959e9f35207a5fe1c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004500000000bb5aaeb821928ef77ebdc0fe2e8c78fcec7e77cb7f7ab4892c33308605436aea164785e076475f85cbc15d70d937422a12d9a4735d9714ffde7e41fd1776e2321c000000000000000000000000000000000000000000000000000000",
            "nonce": "58458",
            "value": "0",
            "gasPrice": "1059444574",
            "toAddress": "0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789",
            "fromAddress": "0x4337013ef5cd421cef649fc58c1b0defd37bbeb1",
            "receiptRoot": null,
            "triggered_by": ["0x8cd6145d0555b867a708ad92f311bc6d67d41fd2"],
            "receiptStatus": "1",
            "receiptGasUsed": "133305",
            "transactionFee": "0.000141229258937070",
            "transactionIndex": "96",
            "receiptContractAddress": null,
            "receiptCumulativeGasUsed": "11334942"
          }
        ],
        "logs": [
          {
            "data": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
            "topic0": "0x7a270f29ae17e8e2304ff1245deb50c3b6206bca82928d904f3e284d35c5ffd2",
            "topic1": "0xf55e82b32f6d94a13607882f976a45834f2555faa871b1ea93c74ddc50857ede",
            "topic2": "0x0000000000000000000000008cd6145d0555b867a708ad92f311bc6d67d41fd2",
            "topic3": null,
            "address": "0x6666666666667849c56f2850848ce1c4da65c68b",
            "logIndex": "161",
            "triggered_by": ["0x8cd6145d0555b867a708ad92f311bc6d67d41fd2"],
            "transactionHash": "0x433aade80380542c5f7294ba4eb43d08bb09352a605a1e41eeab88a823d4bb95"
          },
          {
            "data": "0x0000000000000000000000000000000000000000000000000000000000000000",
            "topic0": "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925",
            "topic1": "0x0000000000000000000000008cd6145d0555b867a708ad92f311bc6d67d41fd2",
            "topic2": "0x000000000000000000000000ee5ca6275c9cfaec5ad42b40ebdbe6073fbcc4a5",
            "topic3": null,
            "address": "0x1c7d4b196cb0c7b01d743fbc6116a902379c7238",
            "logIndex": "163",
            "triggered_by": [
              "0x8cd6145d0555b867a708ad92f311bc6d67d41fd2",
              "0xee5ca6275c9cfaec5ad42b40ebdbe6073fbcc4a5"
            ],
            "transactionHash": "0x433aade80380542c5f7294ba4eb43d08bb09352a605a1e41eeab88a823d4bb95"
          },
          {
            "data": "0x00000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000085ce15510c570000000000000000000000000000000000000000000000000000000000021e67",
            "topic0": "0x49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f",
            "topic1": "0xf55e82b32f6d94a13607882f976a45834f2555faa871b1ea93c74ddc50857ede",
            "topic2": "0x0000000000000000000000008cd6145d0555b867a708ad92f311bc6d67d41fd2",
            "topic3": "0x0000000000000000000000006666666666667849c56f2850848ce1c4da65c68b",
            "address": "0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789",
            "logIndex": "164",
            "triggered_by": ["0x8cd6145d0555b867a708ad92f311bc6d67d41fd2"],
            "transactionHash": "0x433aade80380542c5f7294ba4eb43d08bb09352a605a1e41eeab88a823d4bb95"
          }
        ],
        "block": {
          "hash": "0x2778642f3ff6ae0dd81b8fa81185ab4f6bd7f7b28197eaba1593808cac0cd985",
          "number": "10233219",
          "timestamp": "1770748632"
        },
        "chainId": "eip155:11155111",
        "retries": 0,
        "confirmed": true,
        "custodianId": "cl7hygkd200063g6l5nn0",
        "environmentId": "8c95033b-f3f0-4a15-a4ae-8938e3087990"
      },
      "type": "EIP_155_TX_V1"
    }
    ```
  </Tab>
</Tabs>

<Note>
  `actualGasCost` and `formattedActualGasCost` are only populated for clients whose custodian has Account Abstraction enabled, and only when the transaction was executed as a user operation. `actualGasCost` is a hex-encoded Wei amount (with `0x` prefix); `formattedActualGasCost` is the same value as a base-10 Wei string. When a gas policy is enabled for the chain, these values reflect the gas you sponsored for that user operation.
</Note>

### `Solana Wallet Transactions`

Once you set up an alert webhook with `Solana Wallet Transactions` as the event, Portal begins monitoring *transfer* transactions for your Portal clients with a Solana address. Any new client created with a Solana address will also trigger notifications for their on-chain transfers. Portal sends one event per subscribed address involved in the transaction, identified by a `triggeredBy` field on the payload.

When a Solana transfer transaction occurs, Portal makes a `POST` request to your configured alert webhook URL with a request body that contains the alert webhook event's details. You will receive 1 alert per subscribed address involved when the transaction is `confirmed`.

<Note>
  Still on the legacy Solana alert webhook events? See [`Solana Wallet Transactions (Legacy)`](#solana-wallet-transactions-legacy) and the [migration guide](/resources/migrating-from-solana-legacy-webhooks).
</Note>

#### Direction

Every Solana alert webhook event includes a `direction` field that describes what the `triggeredBy` address did in the transaction. Use it together with `triggeredBy` to route events without re-deriving the role from the raw transaction data.

<Note>
  If both the sender and the receiver are addresses you've registered with Portal, you'll receive **two events** for the same on-chain transfer — one with `direction: OUTBOUND` and one with `direction: INBOUND`. See [Dual events for internal transfers](#dual-events-for-internal-transfers) for details on correlating the two.
</Note>

| Value       | Meaning                                                                                                                                                                                                            |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `OUTBOUND`  | `triggeredBy` is the sender of the transaction — the one who sent native or non-native tokens, who approved or revoked a delegation, or who executed a delegated transfer.                                         |
| `INBOUND`   | `triggeredBy` is the receiver of the transaction — the one who received tokens, or who was granted approval to spend delegated tokens.                                                                             |
| `DELEGATED` | Only on `Solana Delegated Transfers` events: `triggeredBy` is the owner whose tokens are being moved by the delegate (the source of funds in the delegated transfer, distinct from the delegate that executed it). |

<Note>
  `Solana Wallet Transactions` currently sends alerts for non-delegated transactions with the following use cases:

  1. Receive native tokens (ie. `SOL`) from another address
  2. Send native tokens (ie. `SOL`) to another address
  3. Receive non-native tokens (ie. `USDC`) from another address
  4. Send non-native tokens (ie. `USDC`) to another address

  For delegated transfers (a delegate spending an approved allowance), see [`Solana Delegated Transfers`](#solana-delegated-transfers).
</Note>

<Note>
  Solana alert webhook events support **up to 1,000,000 addresses**. Contact our team if you require additional capacity.
</Note>

<Warning>
  After you create a new Portal client wallet, it can take **up to 4 minutes** before you start to receive Solana transaction events for them.
</Warning>

<Warning>
  **Retries:** When your webhook does not respond to a request, or if you do not respond with a `2XX` status code, Portal will retry the request up to 9 times with exponential backoff (see the [retry schedule](#retry-schedule)). If no successful response occurs after all retries, the webhook event will be marked `FAILED` and you can re-drive it with [`POST /custodians/me/alerts/webhooks/{alertWebhookId}/delivery-events/{deliveryEventId}/retries`](/api-reference/alert-webhooks/retry-an-alert-webhook-delivery-event). See [Delivery event lifecycle](#delivery-event-lifecycle) for details on how manual retries interact with the automatic retry loop.
</Warning>

**Headers:**

| Name               | Type   | Description                                                                                                                                                   |
| ------------------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Content-Type`     | String | `application/json`                                                                                                                                            |
| `X-WEBHOOK-SECRET` | String | You can find the alert webhook secret in the [Portal Admin Dashboard](https://app.portalhq.io) on the `"Webhooks"` page under the `"Alert Webhooks"` section. |

**Request body (examples)**

<Tabs>
  <Tab title="Receive 0.01 SOL">
    ```json theme={null}
    {
      "data": {
        "rawEvents": [
          {
            "fee": 80000,
            "slot": 379135212,
            "type": "TRANSFER",
            "events": {},
            "source": "SYSTEM_PROGRAM",
            "feePayer": "75ZfLXXsSpycDvHTQuHnGQuYgd2ihb6Bu4viiCCQ7P4H",
            "signature": "5hyWP82Fn951zjo5DHWAvAC8NjwU7LAKqR3Rpn7sB1H159koer8BefYSSCRuC7X3xVy6rHvMzM7sYHLBwxTAEeUY",
            "timestamp": 1746565669,
            "triggeredBy": "67vPQk4SMRRmZdcfyRd6kBggyeca6TqWmsAkJatNDHyx",
            "direction": "INBOUND",
            "accountData": [
              {
                "account": "75ZfLXXsSpycDvHTQuHnGQuYgd2ihb6Bu4viiCCQ7P4H",
                "nativeBalanceChange": -10080000,
                "tokenBalanceChanges": []
              },
              {
                "account": "67vPQk4SMRRmZdcfyRd6kBggyeca6TqWmsAkJatNDHyx",
                "nativeBalanceChange": 10000000,
                "tokenBalanceChanges": []
              },
              {
                "account": "11111111111111111111111111111111",
                "nativeBalanceChange": 0,
                "tokenBalanceChanges": []
              },
              {
                "account": "ComputeBudget111111111111111111111111111111",
                "nativeBalanceChange": 0,
                "tokenBalanceChanges": []
              }
            ],
            "description": "75ZfLXXsSpycDvHTQuHnGQuYgd2ihb6Bu4viiCCQ7P4H transferred 0.01 SOL to 67vPQk4SMRRmZdcfyRd6kBggyeca6TqWmsAkJatNDHyx.",
            "instructions": [
              {
                "data": "3b1H8Rq1T3d1",
                "accounts": [],
                "programId": "ComputeBudget111111111111111111111111111111",
                "innerInstructions": []
              },
              {
                "data": "LKoyXd",
                "accounts": [],
                "programId": "ComputeBudget111111111111111111111111111111",
                "innerInstructions": []
              },
              {
                "data": "3Bxs4NN8M2Yn4TLb",
                "accounts": [
                  "75ZfLXXsSpycDvHTQuHnGQuYgd2ihb6Bu4viiCCQ7P4H",
                  "67vPQk4SMRRmZdcfyRd6kBggyeca6TqWmsAkJatNDHyx"
                ],
                "programId": "11111111111111111111111111111111",
                "innerInstructions": []
              }
            ],
            "tokenTransfers": [],
            "nativeTransfers": [
              {
                "amount": 10000000,
                "toUserAccount": "67vPQk4SMRRmZdcfyRd6kBggyeca6TqWmsAkJatNDHyx",
                "fromUserAccount": "75ZfLXXsSpycDvHTQuHnGQuYgd2ihb6Bu4viiCCQ7P4H"
              }
            ],
            "recentBlockhash": "2d7rAqbS4ZFsroMHkxA8kmJdvmoPy4mtZRqCpSNTa1DQ",
            "transactionError": null
          }
        ]
      },
      "metadata": {
        "isDevnet": true,
        "eventTypes": ["TRANSFER"],
        "environmentId": "environmentId"
      },
      "type": "SOLANA_TX_V2"
    }
    ```
  </Tab>

  <Tab title="Send 0.0002 SOL">
    ```json theme={null}
    {
      "data": {
        "rawEvents": [
          {
            "fee": 5000,
            "slot": 379135804,
            "type": "TRANSFER",
            "events": {},
            "source": "SYSTEM_PROGRAM",
            "feePayer": "67vPQk4SMRRmZdcfyRd6kBggyeca6TqWmsAkJatNDHyx",
            "signature": "KENcRJpHNowJcpDwPvB2yR6VnDzMbQu5Nd1om5K1Y3cjdS3ujStBWYtz3EMhY1UvzykShhdZSUShwZrmYWbxfAF",
            "timestamp": 1746565903,
            "triggeredBy": "67vPQk4SMRRmZdcfyRd6kBggyeca6TqWmsAkJatNDHyx",
            "direction": "OUTBOUND",
            "accountData": [
              {
                "account": "67vPQk4SMRRmZdcfyRd6kBggyeca6TqWmsAkJatNDHyx",
                "nativeBalanceChange": -205000,
                "tokenBalanceChanges": []
              },
              {
                "account": "75ZfLXXsSpycDvHTQuHnGQuYgd2ihb6Bu4viiCCQ7P4H",
                "nativeBalanceChange": 200000,
                "tokenBalanceChanges": []
              },
              {
                "account": "11111111111111111111111111111111",
                "nativeBalanceChange": 0,
                "tokenBalanceChanges": []
              }
            ],
            "description": "67vPQk4SMRRmZdcfyRd6kBggyeca6TqWmsAkJatNDHyx transferred 0.0002 SOL to 75ZfLXXsSpycDvHTQuHnGQuYgd2ihb6Bu4viiCCQ7P4H.",
            "instructions": [
              {
                "data": "3Bxs4Ba2u7BkmhYB",
                "accounts": [
                  "67vPQk4SMRRmZdcfyRd6kBggyeca6TqWmsAkJatNDHyx",
                  "75ZfLXXsSpycDvHTQuHnGQuYgd2ihb6Bu4viiCCQ7P4H"
                ],
                "programId": "11111111111111111111111111111111",
                "innerInstructions": []
              }
            ],
            "tokenTransfers": [],
            "nativeTransfers": [
              {
                "amount": 200000,
                "toUserAccount": "75ZfLXXsSpycDvHTQuHnGQuYgd2ihb6Bu4viiCCQ7P4H",
                "fromUserAccount": "67vPQk4SMRRmZdcfyRd6kBggyeca6TqWmsAkJatNDHyx"
              }
            ],
            "recentBlockhash": "2d7rAqbS4ZFsroMHkxA8kmJdvmoPy4mtZRqCpSNTa1DQ",
            "transactionError": null
          }
        ]
      },
      "metadata": {
        "isDevnet": true,
        "eventTypes": ["TRANSFER"],
        "environmentId": "environmentId"
      },
      "type": "SOLANA_TX_V2"
    }
    ```
  </Tab>

  <Tab title="Receive 0.0001 USDC">
    ```json theme={null}
    {
      "data": {
        "rawEvents": [
          {
            "fee": 80001,
            "slot": 413214028,
            "type": "TRANSFER",
            "events": {},
            "source": "SOLANA_PROGRAM_LIBRARY",
            "feePayer": "9Nmpej98v2kEAXtc4AD52EbQAFrc3ikRe881LehqsEHD",
            "signature": "3Cek3WrggVJ6PgwMXgnEZS4pyCqbLpZeeo2HWhJYB1McZoKZvyQU497F2yRnTJ3UWBoKcpeL8rd3Bw4pu4FKxyNo",
            "timestamp": 1776189489,
            "triggeredBy": "49bNwvPHy3krxWLBU3qrK5Gm1RPFqWDXB8xaawcjyCjd",
            "direction": "INBOUND",
            "accountData": [
              {
                "account": "9Nmpej98v2kEAXtc4AD52EbQAFrc3ikRe881LehqsEHD",
                "nativeBalanceChange": -80001,
                "tokenBalanceChanges": []
              },
              {
                "account": "C2NWfgp2qUb2papQzy28XQPCxeT84zDLSPJRZvXGeZBp",
                "nativeBalanceChange": 0,
                "tokenBalanceChanges": [
                  {
                    "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
                    "userAccount": "49bNwvPHy3krxWLBU3qrK5Gm1RPFqWDXB8xaawcjyCjd",
                    "tokenAccount": "C2NWfgp2qUb2papQzy28XQPCxeT84zDLSPJRZvXGeZBp",
                    "rawTokenAmount": {
                      "decimals": 6,
                      "tokenAmount": "100"
                    }
                  }
                ]
              },
              {
                "account": "C8wu1R39Brrbf8BrKB6uNQbv1pr43RopsQ2LknfdHa6A",
                "nativeBalanceChange": 0,
                "tokenBalanceChanges": [
                  {
                    "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
                    "userAccount": "9Nmpej98v2kEAXtc4AD52EbQAFrc3ikRe881LehqsEHD",
                    "tokenAccount": "C8wu1R39Brrbf8BrKB6uNQbv1pr43RopsQ2LknfdHa6A",
                    "rawTokenAmount": {
                      "decimals": 6,
                      "tokenAmount": "-100"
                    }
                  }
                ]
              },
              {
                "account": "ComputeBudget111111111111111111111111111111",
                "nativeBalanceChange": 0,
                "tokenBalanceChanges": []
              },
              {
                "account": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
                "nativeBalanceChange": 0,
                "tokenBalanceChanges": []
              },
              {
                "account": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
                "nativeBalanceChange": 0,
                "tokenBalanceChanges": []
              }
            ],
            "description": "9Nmpej98v2kEAXtc4AD52EbQAFrc3ikRe881LehqsEHD transferred 0.0001 USDC to 49bNwvPHy3krxWLBU3qrK5Gm1RPFqWDXB8xaawcjyCjd.",
            "instructions": [
              {
                "data": "3TQPSi2v9sgs",
                "accounts": [],
                "programId": "ComputeBudget111111111111111111111111111111",
                "innerInstructions": []
              },
              {
                "data": "J9y72F",
                "accounts": [],
                "programId": "ComputeBudget111111111111111111111111111111",
                "innerInstructions": []
              },
              {
                "data": "hNmtbNYibdzwf",
                "accounts": [
                  "C8wu1R39Brrbf8BrKB6uNQbv1pr43RopsQ2LknfdHa6A",
                  "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
                  "C2NWfgp2qUb2papQzy28XQPCxeT84zDLSPJRZvXGeZBp",
                  "9Nmpej98v2kEAXtc4AD52EbQAFrc3ikRe881LehqsEHD",
                  "9Nmpej98v2kEAXtc4AD52EbQAFrc3ikRe881LehqsEHD"
                ],
                "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
                "innerInstructions": []
              }
            ],
            "tokenTransfers": [
              {
                "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
                "tokenAmount": 0.0001,
                "toUserAccount": "49bNwvPHy3krxWLBU3qrK5Gm1RPFqWDXB8xaawcjyCjd",
                "tokenStandard": "Fungible",
                "toTokenAccount": "C2NWfgp2qUb2papQzy28XQPCxeT84zDLSPJRZvXGeZBp",
                "fromUserAccount": "9Nmpej98v2kEAXtc4AD52EbQAFrc3ikRe881LehqsEHD",
                "fromTokenAccount": "C8wu1R39Brrbf8BrKB6uNQbv1pr43RopsQ2LknfdHa6A"
              }
            ],
            "nativeTransfers": [],
            "recentBlockhash": "2d7rAqbS4ZFsroMHkxA8kmJdvmoPy4mtZRqCpSNTa1DQ",
            "transactionError": null
          }
        ]
      },
      "metadata": {
        "isDevnet": false,
        "eventTypes": ["TRANSFER"],
        "environmentId": "environmentId"
      },
      "type": "SOLANA_TX_V2"
    }
    ```
  </Tab>
</Tabs>

### `Solana Token Approvals`

Once you configure an alert webhook with `Solana Token Approvals` selected as an event, Portal sends a notification any time a Portal client approves a delegated address to spend an amount of a token on their behalf for the specified chain. Portal delivers one event per subscribed address involved and includes a [`direction`](#direction) field — `OUTBOUND` for the owner approving the delegation, `INBOUND` for the delegate being approved to spend.

When a token approval occurs, Portal makes a `POST` request to your configured alert webhook URL with a request body that contains the alert webhook event's details. You will receive 1 alert per subscribed address involved when the transaction is `confirmed`.

<Warning>
  **Retries:** Solana alert webhook events use exponential backoff with up to 9 retries. See the [retry schedule](#retry-schedule) for details. If no successful response occurs after all retries, the webhook event will be marked `FAILED` and you can re-drive it with [`POST /custodians/me/alerts/webhooks/{alertWebhookId}/delivery-events/{deliveryEventId}/retries`](/api-reference/alert-webhooks/retry-an-alert-webhook-delivery-event). See [Delivery event lifecycle](#delivery-event-lifecycle) for details on how manual retries interact with the automatic retry loop.
</Warning>

**Headers:**

| Name               | Type   | Description                                                                                                                                                   |
| ------------------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Content-Type`     | String | `application/json`                                                                                                                                            |
| `X-WEBHOOK-SECRET` | String | You can find the alert webhook secret in the [Portal Admin Dashboard](https://app.portalhq.io) on the `"Webhooks"` page under the `"Alert Webhooks"` section. |

**Request body (examples)**

```json theme={null}
{
  "data": {
    "rawEvents": [
      {
        "fee": 10000,
        "slot": 438851116,
        "type": "APPROVE",
        "events": {},
        "source": "SOLANA_PROGRAM_LIBRARY",
        "feePayer": "98aauPkabddWQVyHWNw5LJLDb84ETKfN2xYUy1Zr2sQy",
        "signature": "7fLggf2PBHnH8QfWTzWjjpXKcyUj9h2roR6tCBxAAHJoiLyuYEDx95RqJNz1vnjFSJ8vyaZpanCgEHVLdVvX9Ng",
        "timestamp": 1769837881,
        "triggeredBy": "J4AVMHNUkxhjUmWg25RTfysAEx7FnRYymT2xZkc12sKK",
        "direction": "OUTBOUND",
        "accountData": [
          {
            "account": "98aauPkabddWQVyHWNw5LJLDb84ETKfN2xYUy1Zr2sQy",
            "nativeBalanceChange": -10000,
            "tokenBalanceChanges": []
          },
          {
            "account": "J4AVMHNUkxhjUmWg25RTfysAEx7FnRYymT2xZkc12sKK",
            "nativeBalanceChange": 0,
            "tokenBalanceChanges": []
          },
          {
            "account": "pQxS4DANX5uMkDkzJSk2dSBnrEWZ1enUL2XkKF6Nhbh",
            "nativeBalanceChange": 0,
            "tokenBalanceChanges": []
          },
          {
            "account": "75ZfLXXsSpycDvHTQuHnGQuYgd2ihb6Bu4viiCCQ7P4H",
            "nativeBalanceChange": 0,
            "tokenBalanceChanges": []
          },
          {
            "account": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
            "nativeBalanceChange": 0,
            "tokenBalanceChanges": []
          }
        ],
        "description": "",
        "instructions": [
          {
            "data": "3xHiPX82ia9u",
            "accounts": [
              "pQxS4DANX5uMkDkzJSk2dSBnrEWZ1enUL2XkKF6Nhbh",
              "75ZfLXXsSpycDvHTQuHnGQuYgd2ihb6Bu4viiCCQ7P4H",
              "J4AVMHNUkxhjUmWg25RTfysAEx7FnRYymT2xZkc12sKK"
            ],
            "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
            "innerInstructions": []
          }
        ],
        "tokenTransfers": [],
        "nativeTransfers": [],
        "recentBlockhash": "2d7rAqbS4ZFsroMHkxA8kmJdvmoPy4mtZRqCpSNTa1DQ",
        "transactionError": null
      }
    ]
  },
  "metadata": {
    "isDevnet": true,
    "eventTypes": ["APPROVE"],
    "environmentId": "environmentId"
  },
  "type": "SOLANA_APPROVE_V2"
}
```

### `Solana Token Revocations`

Once you configure an alert webhook with `Solana Token Revocations` selected as an event, Portal sends a notification any time a Portal client revokes a delegated address's approval to spend an SPL token on their behalf for the specified chain. Portal delivers one event per subscribed address involved and includes a [`direction`](#direction) field, which is always `OUTBOUND` for revocations.

When a token revocation occurs, Portal makes a `POST` request to your configured alert webhook URL with a request body that contains the alert webhook event's details. You will receive 1 alert per subscribed address involved when the transaction is `confirmed`.

<Warning>
  **Retries:** Solana alert webhook events use exponential backoff with up to 9 retries. See the [retry schedule](#retry-schedule) for details. If no successful response occurs after all retries, the webhook event will be marked `FAILED` and you can re-drive it with [`POST /custodians/me/alerts/webhooks/{alertWebhookId}/delivery-events/{deliveryEventId}/retries`](/api-reference/alert-webhooks/retry-an-alert-webhook-delivery-event). See [Delivery event lifecycle](#delivery-event-lifecycle) for details on how manual retries interact with the automatic retry loop.
</Warning>

**Headers:**

| Name               | Type   | Description                                                                                                                                                   |
| ------------------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Content-Type`     | String | `application/json`                                                                                                                                            |
| `X-WEBHOOK-SECRET` | String | You can find the alert webhook secret in the [Portal Admin Dashboard](https://app.portalhq.io) on the `"Webhooks"` page under the `"Alert Webhooks"` section. |

**Request body (examples)**

```json theme={null}
{
  "data": {
    "rawEvents": [
      {
        "fee": 10000,
        "slot": 438851202,
        "type": "REVOKE",
        "events": {},
        "source": "SOLANA_PROGRAM_LIBRARY",
        "feePayer": "98aauPkabddWQVyHWNw5LJLDb84ETKfN2xYUy1Zr2sQy",
        "signature": "3p4qVucEtUhZ3GbsQv3ZQS2mTRsziSgJs6B7gre34X2xnG2WPkaNvHuk9wDRJ2kKzKa8M7Rmykpr6wTtYLRuY9oh",
        "timestamp": 1769837914,
        "triggeredBy": "98aauPkabddWQVyHWNw5LJLDb84ETKfN2xYUy1Zr2sQy",
        "direction": "OUTBOUND",
        "accountData": [
          {
            "account": "98aauPkabddWQVyHWNw5LJLDb84ETKfN2xYUy1Zr2sQy",
            "nativeBalanceChange": -10000,
            "tokenBalanceChanges": []
          },
          {
            "account": "J4AVMHNUkxhjUmWg25RTfysAEx7FnRYymT2xZkc12sKK",
            "nativeBalanceChange": 0,
            "tokenBalanceChanges": []
          },
          {
            "account": "pQxS4DANX5uMkDkzJSk2dSBnrEWZ1enUL2XkKF6Nhbh",
            "nativeBalanceChange": 0,
            "tokenBalanceChanges": []
          },
          {
            "account": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
            "nativeBalanceChange": 0,
            "tokenBalanceChanges": []
          }
        ],
        "description": "",
        "instructions": [
          {
            "data": "6",
            "accounts": [
              "pQxS4DANX5uMkDkzJSk2dSBnrEWZ1enUL2XkKF6Nhbh",
              "J4AVMHNUkxhjUmWg25RTfysAEx7FnRYymT2xZkc12sKK"
            ],
            "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
            "innerInstructions": []
          }
        ],
        "tokenTransfers": [],
        "nativeTransfers": [],
        "recentBlockhash": "2d7rAqbS4ZFsroMHkxA8kmJdvmoPy4mtZRqCpSNTa1DQ",
        "transactionError": null
      }
    ]
  },
  "metadata": {
    "isDevnet": true,
    "eventTypes": ["REVOKE"],
    "environmentId": "environmentId"
  },
  "type": "SOLANA_REVOKE_V2"
}
```

### `Solana Delegated Transfers`

`Solana Delegated Transfers` triggers when a user performs a delegated transfer using funds from another user who has approved a token delegation to them.

This event type is useful for tracking when delegated token allowances are actually spent. For example, if User A approves User B to spend their USDC, and User B later transfers some of User A's USDC, you will receive a `SOLANA_DELEGATED_TRANSFER_V2` event.

Each event carries a [`direction`](#direction) field that distinguishes the three roles in a delegated transfer: `OUTBOUND` for the delegate who executed the transfer, `INBOUND` for the recipient of the tokens, and `DELEGATED` for the owner whose tokens were moved. This is the only Solana event where `direction` can be `DELEGATED`.

Once you configure an alert webhook with `Solana Delegated Transfers` selected as an event, Portal sends a notification any time a delegated transfer occurs involving your Portal clients.

When a delegated transfer occurs, Portal makes a `POST` request to your configured alert webhook URL with a request body that contains the alert webhook event's details. You will receive 1 alert per subscribed address involved when the transaction is `confirmed`.

<Warning>
  **Retries:** Solana alert webhook events use exponential backoff with up to 9 retries. See the [retry schedule](#retry-schedule) for details. If no successful response occurs after all retries, the webhook event will be marked `FAILED` and you can re-drive it with [`POST /custodians/me/alerts/webhooks/{alertWebhookId}/delivery-events/{deliveryEventId}/retries`](/api-reference/alert-webhooks/retry-an-alert-webhook-delivery-event). See [Delivery event lifecycle](#delivery-event-lifecycle) for details on how manual retries interact with the automatic retry loop.
</Warning>

**Headers:**

| Name               | Type   | Description                                                                                                                                                   |
| ------------------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Content-Type`     | String | `application/json`                                                                                                                                            |
| `X-WEBHOOK-SECRET` | String | You can find the alert webhook secret in the [Portal Admin Dashboard](https://app.portalhq.io) on the `"Webhooks"` page under the `"Alert Webhooks"` section. |

**Request body (examples)**

```json theme={null}
{
  "data": {
    "rawEvents": [
      {
        "fee": 10000,
        "slot": 413227505,
        "type": "DELEGATED_TRANSFER",
        "events": {},
        "source": "SOLANA_PROGRAM_LIBRARY",
        "feePayer": "2eWxXEVjmyBxaJCPRmZJqtpYm7wkyXfmcNAwFPHBpwLx",
        "signature": "5BFuHM8bMUVfHuwFc4DHoPQsYMbwhYWLGkjv41Lu5dn4wuRb23mK5oDJirjQxKadGYYWzTmSYnUMUed3Reef6Mxx",
        "timestamp": 1776194807,
        "triggeredBy": "9Nmpej98v2kEAXtc4AD52EbQAFrc3ikRe881LehqsEHD",
        "direction": "INBOUND",
        "accountData": [
          {
            "account": "2eWxXEVjmyBxaJCPRmZJqtpYm7wkyXfmcNAwFPHBpwLx",
            "nativeBalanceChange": -10000,
            "tokenBalanceChanges": []
          },
          {
            "account": "9rG4md1zvwpP1NtjMg48HutvuWYaHXerYat2C2E4PAz7",
            "nativeBalanceChange": 0,
            "tokenBalanceChanges": []
          },
          {
            "account": "C2NWfgp2qUb2papQzy28XQPCxeT84zDLSPJRZvXGeZBp",
            "nativeBalanceChange": 0,
            "tokenBalanceChanges": [
              {
                "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
                "userAccount": "49bNwvPHy3krxWLBU3qrK5Gm1RPFqWDXB8xaawcjyCjd",
                "tokenAccount": "C2NWfgp2qUb2papQzy28XQPCxeT84zDLSPJRZvXGeZBp",
                "rawTokenAmount": {
                  "decimals": 6,
                  "tokenAmount": "-1"
                }
              }
            ]
          },
          {
            "account": "C8wu1R39Brrbf8BrKB6uNQbv1pr43RopsQ2LknfdHa6A",
            "nativeBalanceChange": 0,
            "tokenBalanceChanges": [
              {
                "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
                "userAccount": "9Nmpej98v2kEAXtc4AD52EbQAFrc3ikRe881LehqsEHD",
                "tokenAccount": "C8wu1R39Brrbf8BrKB6uNQbv1pr43RopsQ2LknfdHa6A",
                "rawTokenAmount": {
                  "decimals": 6,
                  "tokenAmount": "1"
                }
              }
            ]
          },
          {
            "account": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
            "nativeBalanceChange": 0,
            "tokenBalanceChanges": []
          }
        ],
        "description": "9rG4md1zvwpP1NtjMg48HutvuWYaHXerYat2C2E4PAz7 transferred 0.000001 USDC to 9Nmpej98v2kEAXtc4AD52EbQAFrc3ikRe881LehqsEHD.",
        "instructions": [
          {
            "data": "3DdGGhkhJbjm",
            "accounts": [
              "C2NWfgp2qUb2papQzy28XQPCxeT84zDLSPJRZvXGeZBp",
              "C8wu1R39Brrbf8BrKB6uNQbv1pr43RopsQ2LknfdHa6A",
              "9rG4md1zvwpP1NtjMg48HutvuWYaHXerYat2C2E4PAz7"
            ],
            "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
            "innerInstructions": []
          }
        ],
        "tokenTransfers": [
          {
            "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
            "tokenAmount": 0.000001,
            "toUserAccount": "9Nmpej98v2kEAXtc4AD52EbQAFrc3ikRe881LehqsEHD",
            "tokenStandard": "Fungible",
            "toTokenAccount": "C8wu1R39Brrbf8BrKB6uNQbv1pr43RopsQ2LknfdHa6A",
            "fromUserAccount": "9rG4md1zvwpP1NtjMg48HutvuWYaHXerYat2C2E4PAz7",
            "fromTokenAccount": "C2NWfgp2qUb2papQzy28XQPCxeT84zDLSPJRZvXGeZBp"
          }
        ],
        "nativeTransfers": [],
        "recentBlockhash": "2d7rAqbS4ZFsroMHkxA8kmJdvmoPy4mtZRqCpSNTa1DQ",
        "transactionError": null
      }
    ]
  },
  "metadata": {
    "isDevnet": false,
    "eventTypes": ["DELEGATED_TRANSFER"],
    "environmentId": "environmentId"
  },
  "type": "SOLANA_DELEGATED_TRANSFER_V2"
}
```

## Retry schedule

Each alert type has its own automatic retry policy. The table below summarizes the policy for every event type that ships with delivery events (`PENDING` → `DELIVERED`/`FAILED` lifecycle). Once the automatic retries are exhausted, the delivery event is marked `FAILED` and you can re-drive it manually with [`POST /custodians/me/alerts/webhooks/{alertWebhookId}/delivery-events/{deliveryEventId}/retries`](/api-reference/alert-webhooks/retry-an-alert-webhook-delivery-event) — see [Delivery event lifecycle](#delivery-event-lifecycle).

| Event type                                                    | Automatic retry policy                                                                                  |
| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `EVM Wallet Transactions` (`EIP_155_TX_V1`)                   | Up to 9 retries with exponential backoff. See the [backoff schedule](#backoff-schedule) below.          |
| `Solana Wallet Transactions` (`SOLANA_TX_V2`)                 | Up to 9 retries with exponential backoff. See the [backoff schedule](#backoff-schedule) below.          |
| `Solana Token Approvals` (`SOLANA_APPROVE_V2`)                | Up to 9 retries with exponential backoff. See the [backoff schedule](#backoff-schedule) below.          |
| `Solana Token Revocations` (`SOLANA_REVOKE_V2`)               | Up to 9 retries with exponential backoff. See the [backoff schedule](#backoff-schedule) below.          |
| `Solana Delegated Transfers` (`SOLANA_DELEGATED_TRANSFER_V2`) | Up to 9 retries with exponential backoff. See the [backoff schedule](#backoff-schedule) below.          |
| `Solana Wallet Transactions (Legacy)` (`SOLANA_TX_V1`)        | Up to 9 retries with exponential backoff. See the [backoff schedule](#backoff-schedule) below.          |
| `Solana Token Approvals (Legacy)` (`SOLANA_APPROVE_V1`)       | Up to 9 retries with exponential backoff. See the [backoff schedule](#backoff-schedule) below.          |
| `Solana Token Revocations (Legacy)` (`SOLANA_REVOKE_V1`)      | Up to 9 retries with exponential backoff. See the [backoff schedule](#backoff-schedule) below.          |
| `Wallet Created` (`WALLET_GENERATE_V1`)                       | Up to 9 retries with exponential backoff. See the [backoff schedule](#backoff-schedule) below.          |
| `Wallet Backed Up` (`WALLET_BACKUP_V1`)                       | Up to 9 retries with exponential backoff. See the [backoff schedule](#backoff-schedule) below.          |
| `Wallet Recovered` (`WALLET_RECOVER_V1`)                      | Up to 9 retries with exponential backoff. See the [backoff schedule](#backoff-schedule) below.          |
| `Wallet Ejected` (`WALLET_EJECT_V1`)                          | Single synchronous attempt. No automatic retries.                                                       |
| `Signature Approvals` (`PRE_SIGN_V1`)                         | Single synchronous attempt. No automatic retries — the signing decision is made from that one response. |

### Backoff schedule

All EVM, Solana V2, and legacy Solana V1 alert types share the same exponential backoff:

| Retry | Backoff    |
| ----- | ---------- |
| 1     | 4 minutes  |
| 2     | 8 minutes  |
| 3     | 15 minutes |
| 4     | 30 minutes |
| 5     | 1 hour     |
| 6     | 2 hours    |
| 7     | 4 hours    |
| 8     | 8 hours    |
| 9     | 16 hours   |

## Solana Alert Webhook Events (Legacy)

<Note>
  Already integrated against the V1 events below? Follow the [migration guide for legacy Solana alert webhook events](/resources/migrating-from-solana-legacy-webhooks) to move to the current alerts and to subscribe to the new `Solana Delegated Transfers` event.
</Note>

### Why migrate from V1 to V2?

We recommend moving from V1 to the current Solana alert webhook events. V1 will be deprecated in the near future, and the current events give you per-address delivery (with a `triggeredBy` field for routing), a `direction` field for parsing the role of each address, and the new `Solana Delegated Transfers` event for tracking delegate spends. See the [migration guide for legacy Solana alert webhook events](/resources/migrating-from-solana-legacy-webhooks) for the cutover steps.

|                            | V1                                | V2                                                                                   |
| -------------------------- | --------------------------------- | ------------------------------------------------------------------------------------ |
| **Delivery model**         | One event per Solana transaction  | One event per subscribed address involved in the transaction                         |
| **Address identification** | No `triggeredBy` field            | Includes `triggeredBy` — the address that triggered the event                        |
| **Direction parsing**      | No `direction` field              | Includes `direction` (`OUTBOUND`, `INBOUND`, or `DELEGATED`)                         |
| **Delegated transfers**    | Not delivered as a separate event | New `Solana Delegated Transfers` event when a delegate spends an approved allowance  |
| **More data included**     | No `recentBlockhash` field        | Includes `recentBlockhash` — the base58-encoded `recentBlockhash` of the transaction |
| **Event type**             | `SOLANA_TX_V1`                    | `SOLANA_TX_V2`                                                                       |

In V1, if a transaction involves two of your Portal clients (e.g. one sends SOL to the other), you receive a single webhook event for that transaction. In V2, you receive one event per subscribed address involved — so you would receive two separate events for the same transaction, each with a different `triggeredBy` value. You can use the `triggeredBy` field along with the transaction `signature` to deduplicate events on your end if needed.

### `Solana Wallet Transactions (Legacy)`

<Warning>
  **Deprecation notice:** `Solana Wallet Transactions` (V1) will be deprecated in the near future. We recommend [migrating from the legacy Solana alert webhook events](/resources/migrating-from-solana-legacy-webhooks) as soon as possible. V2 delivers one event per subscribed address involved in the transaction (instead of one event per transaction) and includes a `triggeredBy` field for easier deduplication.
</Warning>

Once you set up an alert webhook with `Solana Wallet Transactions` as the event, Portal begins monitoring *transfer* transactions for your Portal clients with a Solana address. Any new client created with a Solana address will also trigger notifications for their on-chain transfers.

When a Solana transfer transaction occurs, Portal makes a `POST` request to your configured alert webhook URL with a request body that contains the alert webhook event's details. You will receive 1 alert when the transaction is `confirmed`.

<Note>
  `Solana Wallet Transactions` currently sends alerts with the following use cases:

  1. Receive native tokens (ie. `SOL`) from another address
  2. Send native tokens (ie. `SOL`) to another address
  3. Receive non-native tokens (ie. `USDC`) from another address
  4. Send non-native tokens (ie. `USDC`) to another address
</Note>

<Note>
  Solana alert webhook events support **up to 100,000 addresses**. Contact our team if you require additional capacity.
</Note>

<Warning>
  After you create a new Portal client wallet, it can take **up to 4 minutes** before you start to receive Solana transaction events for them.
</Warning>

<Warning>
  **Retries:** When your webhook does not respond to a request, or if you do not respond with a `2XX` status code, Portal will retry the request up to 9 times with exponential backoff (see the [retry schedule](#retry-schedule)). If no successful response occurs after all retries, the webhook event will be marked `FAILED` and you can re-drive it with [`POST /custodians/me/alerts/webhooks/{alertWebhookId}/delivery-events/{deliveryEventId}/retries`](/api-reference/alert-webhooks/retry-an-alert-webhook-delivery-event). See [Delivery event lifecycle](#delivery-event-lifecycle) for details on how manual retries interact with the automatic retry loop.
</Warning>

**Headers:**

| Name               | Type   | Description                                                                                                                                                   |
| ------------------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Content-Type`     | String | `application/json`                                                                                                                                            |
| `X-WEBHOOK-SECRET` | String | You can find the alert webhook secret in the [Portal Admin Dashboard](https://app.portalhq.io) on the `"Webhooks"` page under the `"Alert Webhooks"` section. |

**Request body (examples)**

<Tabs>
  <Tab title="Receive 0.01 SOL">
    ```json theme={null}
    {
      "data": {
        "rawEvents": [
          {
            "fee": 80000,
            "slot": 379135212,
            "type": "TRANSFER",
            "events": {},
            "source": "SYSTEM_PROGRAM",
            "feePayer": "75ZfLXXsSpycDvHTQuHnGQuYgd2ihb6Bu4viiCCQ7P4H",
            "signature": "5hyWP82Fn951zjo5DHWAvAC8NjwU7LAKqR3Rpn7sB1H159koer8BefYSSCRuC7X3xVy6rHvMzM7sYHLBwxTAEeUY",
            "timestamp": 1746565669,
            "accountData": [
              {
                "account": "75ZfLXXsSpycDvHTQuHnGQuYgd2ihb6Bu4viiCCQ7P4H",
                "nativeBalanceChange": -10080000,
                "tokenBalanceChanges": []
              },
              {
                "account": "67vPQk4SMRRmZdcfyRd6kBggyeca6TqWmsAkJatNDHyx",
                "nativeBalanceChange": 10000000,
                "tokenBalanceChanges": []
              },
              {
                "account": "11111111111111111111111111111111",
                "nativeBalanceChange": 0,
                "tokenBalanceChanges": []
              },
              {
                "account": "ComputeBudget111111111111111111111111111111",
                "nativeBalanceChange": 0,
                "tokenBalanceChanges": []
              }
            ],
            "description": "75ZfLXXsSpycDvHTQuHnGQuYgd2ihb6Bu4viiCCQ7P4H transferred 0.01 SOL to 67vPQk4SMRRmZdcfyRd6kBggyeca6TqWmsAkJatNDHyx.",
            "instructions": [
              {
                "data": "3b1H8Rq1T3d1",
                "accounts": [],
                "programId": "ComputeBudget111111111111111111111111111111",
                "innerInstructions": []
              },
              {
                "data": "LKoyXd",
                "accounts": [],
                "programId": "ComputeBudget111111111111111111111111111111",
                "innerInstructions": []
              },
              {
                "data": "3Bxs4NN8M2Yn4TLb",
                "accounts": [
                  "75ZfLXXsSpycDvHTQuHnGQuYgd2ihb6Bu4viiCCQ7P4H",
                  "67vPQk4SMRRmZdcfyRd6kBggyeca6TqWmsAkJatNDHyx"
                ],
                "programId": "11111111111111111111111111111111",
                "innerInstructions": []
              }
            ],
            "tokenTransfers": [],
            "nativeTransfers": [
              {
                "amount": 10000000,
                "toUserAccount": "67vPQk4SMRRmZdcfyRd6kBggyeca6TqWmsAkJatNDHyx",
                "fromUserAccount": "75ZfLXXsSpycDvHTQuHnGQuYgd2ihb6Bu4viiCCQ7P4H"
              }
            ],
            "transactionError": null
          }
        ]
      },
      "metadata": {
        "isDevnet": true,
        "eventTypes": ["TRANSFER"],
        "environmentId": "environmentId"
      },
      "type": "SOLANA_TX_V1"
    }
    ```
  </Tab>

  <Tab title="Send 0.0002 SOL">
    ```json theme={null}
    {
      "data": {
        "rawEvents": [
          {
            "fee": 5000,
            "slot": 379135804,
            "type": "TRANSFER",
            "events": {},
            "source": "SYSTEM_PROGRAM",
            "feePayer": "67vPQk4SMRRmZdcfyRd6kBggyeca6TqWmsAkJatNDHyx",
            "signature": "KENcRJpHNowJcpDwPvB2yR6VnDzMbQu5Nd1om5K1Y3cjdS3ujStBWYtz3EMhY1UvzykShhdZSUShwZrmYWbxfAF",
            "timestamp": 1746565903,
            "accountData": [
              {
                "account": "67vPQk4SMRRmZdcfyRd6kBggyeca6TqWmsAkJatNDHyx",
                "nativeBalanceChange": -205000,
                "tokenBalanceChanges": []
              },
              {
                "account": "75ZfLXXsSpycDvHTQuHnGQuYgd2ihb6Bu4viiCCQ7P4H",
                "nativeBalanceChange": 200000,
                "tokenBalanceChanges": []
              },
              {
                "account": "11111111111111111111111111111111",
                "nativeBalanceChange": 0,
                "tokenBalanceChanges": []
              }
            ],
            "description": "67vPQk4SMRRmZdcfyRd6kBggyeca6TqWmsAkJatNDHyx transferred 0.0002 SOL to 75ZfLXXsSpycDvHTQuHnGQuYgd2ihb6Bu4viiCCQ7P4H.",
            "instructions": [
              {
                "data": "3Bxs4Ba2u7BkmhYB",
                "accounts": [
                  "67vPQk4SMRRmZdcfyRd6kBggyeca6TqWmsAkJatNDHyx",
                  "75ZfLXXsSpycDvHTQuHnGQuYgd2ihb6Bu4viiCCQ7P4H"
                ],
                "programId": "11111111111111111111111111111111",
                "innerInstructions": []
              }
            ],
            "tokenTransfers": [],
            "nativeTransfers": [
              {
                "amount": 200000,
                "toUserAccount": "75ZfLXXsSpycDvHTQuHnGQuYgd2ihb6Bu4viiCCQ7P4H",
                "fromUserAccount": "67vPQk4SMRRmZdcfyRd6kBggyeca6TqWmsAkJatNDHyx"
              }
            ],
            "transactionError": null
          }
        ]
      },
      "metadata": {
        "isDevnet": true,
        "eventTypes": ["TRANSFER"],
        "environmentId": "environmentId"
      },
      "type": "SOLANA_TX_V1"
    }
    ```
  </Tab>

  <Tab title="Receive 0.1 USDC">
    ```json theme={null}
    {
      "data": {
        "rawEvents": [
          {
            "fee": 80000,
            "slot": 379136813,
            "type": "TRANSFER",
            "events": {},
            "source": "SOLANA_PROGRAM_LIBRARY",
            "feePayer": "75ZfLXXsSpycDvHTQuHnGQuYgd2ihb6Bu4viiCCQ7P4H",
            "signature": "3AH7LPWnzvqmQRkGWMECe7Hxmi6M4FurCrGxAfS4JP7YNVz81qQhwsX7YmHdQxA1bfGFPDc8ouHXuhncXPnT3Uf9",
            "timestamp": 1746566300,
            "accountData": [
              {
                "account": "75ZfLXXsSpycDvHTQuHnGQuYgd2ihb6Bu4viiCCQ7P4H",
                "nativeBalanceChange": -2119280,
                "tokenBalanceChanges": []
              },
              {
                "account": "4hi76XkyLMVtuPunTUH8SMYGbKshhcGufSfZEUkWvj9v",
                "nativeBalanceChange": 2039280,
                "tokenBalanceChanges": [
                  {
                    "mint": "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
                    "userAccount": "67vPQk4SMRRmZdcfyRd6kBggyeca6TqWmsAkJatNDHyx",
                    "tokenAccount": "4hi76XkyLMVtuPunTUH8SMYGbKshhcGufSfZEUkWvj9v",
                    "rawTokenAmount": {
                      "decimals": 6,
                      "tokenAmount": "100000"
                    }
                  }
                ]
              },
              {
                "account": "CoDtJspas5BkRaxA8GTrzLZGTAMcsoec4oTrrVjwrJqA",
                "nativeBalanceChange": 0,
                "tokenBalanceChanges": [
                  {
                    "mint": "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
                    "userAccount": "75ZfLXXsSpycDvHTQuHnGQuYgd2ihb6Bu4viiCCQ7P4H",
                    "tokenAccount": "CoDtJspas5BkRaxA8GTrzLZGTAMcsoec4oTrrVjwrJqA",
                    "rawTokenAmount": {
                      "decimals": 6,
                      "tokenAmount": "-100000"
                    }
                  }
                ]
              },
              {
                "account": "11111111111111111111111111111111",
                "nativeBalanceChange": 0,
                "tokenBalanceChanges": []
              },
              {
                "account": "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
                "nativeBalanceChange": 0,
                "tokenBalanceChanges": []
              },
              {
                "account": "67vPQk4SMRRmZdcfyRd6kBggyeca6TqWmsAkJatNDHyx",
                "nativeBalanceChange": 0,
                "tokenBalanceChanges": []
              },
              {
                "account": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL",
                "nativeBalanceChange": 0,
                "tokenBalanceChanges": []
              },
              {
                "account": "ComputeBudget111111111111111111111111111111",
                "nativeBalanceChange": 0,
                "tokenBalanceChanges": []
              },
              {
                "account": "SysvarRent111111111111111111111111111111111",
                "nativeBalanceChange": 0,
                "tokenBalanceChanges": []
              },
              {
                "account": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
                "nativeBalanceChange": 0,
                "tokenBalanceChanges": []
              }
            ],
            "description": "75ZfLXXsSpycDvHTQuHnGQuYgd2ihb6Bu4viiCCQ7P4H transferred 0.1 4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU to 67vPQk4SMRRmZdcfyRd6kBggyeca6TqWmsAkJatNDHyx.",
            "instructions": [
              {
                "data": "3pyz49iBVPDH",
                "accounts": [],
                "programId": "ComputeBudget111111111111111111111111111111",
                "innerInstructions": []
              },
              {
                "data": "Ejyj83",
                "accounts": [],
                "programId": "ComputeBudget111111111111111111111111111111",
                "innerInstructions": []
              },
              {
                "data": "",
                "accounts": [
                  "75ZfLXXsSpycDvHTQuHnGQuYgd2ihb6Bu4viiCCQ7P4H",
                  "4hi76XkyLMVtuPunTUH8SMYGbKshhcGufSfZEUkWvj9v",
                  "67vPQk4SMRRmZdcfyRd6kBggyeca6TqWmsAkJatNDHyx",
                  "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
                  "11111111111111111111111111111111",
                  "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
                  "SysvarRent111111111111111111111111111111111"
                ],
                "programId": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL",
                "innerInstructions": [
                  {
                    "data": "84eT",
                    "accounts": [
                      "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU"
                    ],
                    "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
                  },
                  {
                    "data": "11119os1e9qSs2u7TsThXqkBSRVFxhmYaFKFZ1waB2X7armDmvK3p5GmLdUxYdg3h7QSrL",
                    "accounts": [
                      "75ZfLXXsSpycDvHTQuHnGQuYgd2ihb6Bu4viiCCQ7P4H",
                      "4hi76XkyLMVtuPunTUH8SMYGbKshhcGufSfZEUkWvj9v"
                    ],
                    "programId": "11111111111111111111111111111111"
                  },
                  {
                    "data": "P",
                    "accounts": [
                      "4hi76XkyLMVtuPunTUH8SMYGbKshhcGufSfZEUkWvj9v"
                    ],
                    "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
                  },
                  {
                    "data": "6SFd4LwbEeGhbq7iC4apSEA85UYCPWD7DUnLboAaBWSQv",
                    "accounts": [
                      "4hi76XkyLMVtuPunTUH8SMYGbKshhcGufSfZEUkWvj9v",
                      "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU"
                    ],
                    "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
                  }
                ]
              },
              {
                "data": "i9TTqffgKmDLh",
                "accounts": [
                  "CoDtJspas5BkRaxA8GTrzLZGTAMcsoec4oTrrVjwrJqA",
                  "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
                  "4hi76XkyLMVtuPunTUH8SMYGbKshhcGufSfZEUkWvj9v",
                  "75ZfLXXsSpycDvHTQuHnGQuYgd2ihb6Bu4viiCCQ7P4H",
                  "75ZfLXXsSpycDvHTQuHnGQuYgd2ihb6Bu4viiCCQ7P4H"
                ],
                "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
                "innerInstructions": []
              }
            ],
            "tokenTransfers": [
              {
                "mint": "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
                "tokenAmount": 0.1,
                "toUserAccount": "67vPQk4SMRRmZdcfyRd6kBggyeca6TqWmsAkJatNDHyx",
                "tokenStandard": "Fungible",
                "toTokenAccount": "4hi76XkyLMVtuPunTUH8SMYGbKshhcGufSfZEUkWvj9v",
                "fromUserAccount": "75ZfLXXsSpycDvHTQuHnGQuYgd2ihb6Bu4viiCCQ7P4H",
                "fromTokenAccount": "CoDtJspas5BkRaxA8GTrzLZGTAMcsoec4oTrrVjwrJqA"
              }
            ],
            "nativeTransfers": [
              {
                "amount": 2039280,
                "toUserAccount": "4hi76XkyLMVtuPunTUH8SMYGbKshhcGufSfZEUkWvj9v",
                "fromUserAccount": "75ZfLXXsSpycDvHTQuHnGQuYgd2ihb6Bu4viiCCQ7P4H"
              }
            ],
            "transactionError": null
          }
        ]
      },
      "metadata": {
        "isDevnet": true,
        "eventTypes": ["TRANSFER"],
        "environmentId": "environmentId"
      },
      "type": "SOLANA_TX_V1"
    }
    ```
  </Tab>

  <Tab title="Send 0.01 USDC">
    ```json theme={null}
    {
      "data": {
        "rawEvents": [
          {
            "fee": 5000,
            "slot": 379137198,
            "type": "TRANSFER",
            "events": {},
            "source": "SOLANA_PROGRAM_LIBRARY",
            "feePayer": "67vPQk4SMRRmZdcfyRd6kBggyeca6TqWmsAkJatNDHyx",
            "signature": "yqQnyoqB1cm5se1U4Da6Hr89mNvozzp5WULtkNSUP3jVH5c79whZ9zg2NXeQhV3c3wkmsVjPV627WZtfCaAeRJW",
            "timestamp": 1746566451,
            "accountData": [
              {
                "account": "67vPQk4SMRRmZdcfyRd6kBggyeca6TqWmsAkJatNDHyx",
                "nativeBalanceChange": -5000,
                "tokenBalanceChanges": []
              },
              {
                "account": "4hi76XkyLMVtuPunTUH8SMYGbKshhcGufSfZEUkWvj9v",
                "nativeBalanceChange": 0,
                "tokenBalanceChanges": [
                  {
                    "mint": "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
                    "userAccount": "67vPQk4SMRRmZdcfyRd6kBggyeca6TqWmsAkJatNDHyx",
                    "tokenAccount": "4hi76XkyLMVtuPunTUH8SMYGbKshhcGufSfZEUkWvj9v",
                    "rawTokenAmount": {
                      "decimals": 6,
                      "tokenAmount": "-10000"
                    }
                  }
                ]
              },
              {
                "account": "CoDtJspas5BkRaxA8GTrzLZGTAMcsoec4oTrrVjwrJqA",
                "nativeBalanceChange": 0,
                "tokenBalanceChanges": [
                  {
                    "mint": "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
                    "userAccount": "75ZfLXXsSpycDvHTQuHnGQuYgd2ihb6Bu4viiCCQ7P4H",
                    "tokenAccount": "CoDtJspas5BkRaxA8GTrzLZGTAMcsoec4oTrrVjwrJqA",
                    "rawTokenAmount": {
                      "decimals": 6,
                      "tokenAmount": "10000"
                    }
                  }
                ]
              },
              {
                "account": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
                "nativeBalanceChange": 0,
                "tokenBalanceChanges": []
              }
            ],
            "description": "67vPQk4SMRRmZdcfyRd6kBggyeca6TqWmsAkJatNDHyx transferred 0.01 4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU to 75ZfLXXsSpycDvHTQuHnGQuYgd2ihb6Bu4viiCCQ7P4H.",
            "instructions": [
              {
                "data": "3GAG5eogvTjV",
                "accounts": [
                  "4hi76XkyLMVtuPunTUH8SMYGbKshhcGufSfZEUkWvj9v",
                  "CoDtJspas5BkRaxA8GTrzLZGTAMcsoec4oTrrVjwrJqA",
                  "67vPQk4SMRRmZdcfyRd6kBggyeca6TqWmsAkJatNDHyx"
                ],
                "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
                "innerInstructions": []
              }
            ],
            "tokenTransfers": [
              {
                "mint": "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
                "tokenAmount": 0.01,
                "toUserAccount": "75ZfLXXsSpycDvHTQuHnGQuYgd2ihb6Bu4viiCCQ7P4H",
                "tokenStandard": "Fungible",
                "toTokenAccount": "CoDtJspas5BkRaxA8GTrzLZGTAMcsoec4oTrrVjwrJqA",
                "fromUserAccount": "67vPQk4SMRRmZdcfyRd6kBggyeca6TqWmsAkJatNDHyx",
                "fromTokenAccount": "4hi76XkyLMVtuPunTUH8SMYGbKshhcGufSfZEUkWvj9v"
              }
            ],
            "nativeTransfers": [],
            "transactionError": null
          }
        ]
      },
      "metadata": {
        "isDevnet": true,
        "eventTypes": ["TRANSFER"],
        "environmentId": "environmentId"
      },
      "type": "SOLANA_TX_V1"
    }
    ```
  </Tab>
</Tabs>

### `Solana Token Approvals (Legacy)`

<Warning>
  **Deprecation notice:** `Solana Token Approvals` (V1) will be deprecated in the near future. We recommend [migrating from the legacy Solana alert webhook events](/resources/migrating-from-solana-legacy-webhooks) as soon as possible.
</Warning>

Once you configure an alert webhook with `Solana Token Approvals` selected as an event, Portal sends a notification any time a Portal client approves a delegated address to spend an amount of a token on their behalf for the specified chain.

When a token approval occurs, Portal makes a `POST` request to your configured alert webhook URL with a request body that contains the alert webhook event's details. You will receive 1 alert when the transaction is `confirmed`.

<Warning>
  **Retries:** When your webhook does not respond to a request, or if you do not respond with a `2XX` status code, Portal will retry the request up to 9 times with exponential backoff (see the [retry schedule](#retry-schedule)). If no successful response occurs after all retries, the webhook event will be marked `FAILED` and you can re-drive it with [`POST /custodians/me/alerts/webhooks/{alertWebhookId}/delivery-events/{deliveryEventId}/retries`](/api-reference/alert-webhooks/retry-an-alert-webhook-delivery-event). See [Delivery event lifecycle](#delivery-event-lifecycle) for details on how manual retries interact with the automatic retry loop.
</Warning>

**Headers:**

| Name               | Type   | Description                                                                                                                                                   |
| ------------------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Content-Type`     | String | `application/json`                                                                                                                                            |
| `X-WEBHOOK-SECRET` | String | You can find the alert webhook secret in the [Portal Admin Dashboard](https://app.portalhq.io) on the `"Webhooks"` page under the `"Alert Webhooks"` section. |

**Request body (examples)**

```json theme={null}
{
  "data": {
    "rawEvents": [
      {
        "fee": 10000,
        "slot": 438851116,
        "type": "APPROVE",
        "events": {},
        "source": "SOLANA_PROGRAM_LIBRARY",
        "feePayer": "98aauPkabddWQVyHWNw5LJLDb84ETKfN2xYUy1Zr2sQy",
        "signature": "7fLggf2PBHnH8QfWTzWjjpXKcyUj9h2roR6tCBxAAHJoiLyuYEDx95RqJNz1vnjFSJ8vyaZpanCgEHVLdVvX9Ng",
        "timestamp": 1769837881,
        "accountData": [
          {
            "account": "98aauPkabddWQVyHWNw5LJLDb84ETKfN2xYUy1Zr2sQy",
            "nativeBalanceChange": -10000,
            "tokenBalanceChanges": []
          },
          {
            "account": "J4AVMHNUkxhjUmWg25RTfysAEx7FnRYymT2xZkc12sKK",
            "nativeBalanceChange": 0,
            "tokenBalanceChanges": []
          },
          {
            "account": "pQxS4DANX5uMkDkzJSk2dSBnrEWZ1enUL2XkKF6Nhbh",
            "nativeBalanceChange": 0,
            "tokenBalanceChanges": []
          },
          {
            "account": "75ZfLXXsSpycDvHTQuHnGQuYgd2ihb6Bu4viiCCQ7P4H",
            "nativeBalanceChange": 0,
            "tokenBalanceChanges": []
          },
          {
            "account": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
            "nativeBalanceChange": 0,
            "tokenBalanceChanges": []
          }
        ],
        "description": "",
        "instructions": [
          {
            "data": "3xHiPX82ia9u",
            "accounts": [
              "pQxS4DANX5uMkDkzJSk2dSBnrEWZ1enUL2XkKF6Nhbh",
              "75ZfLXXsSpycDvHTQuHnGQuYgd2ihb6Bu4viiCCQ7P4H",
              "J4AVMHNUkxhjUmWg25RTfysAEx7FnRYymT2xZkc12sKK"
            ],
            "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
            "innerInstructions": []
          }
        ],
        "tokenTransfers": [],
        "nativeTransfers": [],
        "transactionError": null
      }
    ]
  },
  "metadata": {
    "isDevnet": true,
    "eventTypes": ["APPROVE"],
    "environmentId": "environmentId"
  },
  "type": "SOLANA_APPROVAL_V1"
}
```

### `Solana Token Revocations (Legacy)`

<Warning>
  **Deprecation notice:** `Solana Token Revocations` (V1) will be deprecated in the near future. We recommend [migrating from the legacy Solana alert webhook events](/resources/migrating-from-solana-legacy-webhooks) as soon as possible.
</Warning>

Once you configure an alert webhook with `Solana Token Revocations` selected as an event, Portal sends a notification any time a Portal client revokes a delegated address's approval to spend an SPL token on their behalf for the specified chain.

When a token revocation occurs, Portal makes a `POST` request to your configured alert webhook URL with a request body that contains the alert webhook event's details. You will receive 1 alert when the transaction is `confirmed`.

<Warning>
  **Retries:** When your webhook does not respond to a request, or if you do not respond with a `2XX` status code, Portal will retry the request up to 9 times with exponential backoff (see the [retry schedule](#retry-schedule)). If no successful response occurs after all retries, the webhook event will be marked `FAILED` and you can re-drive it with [`POST /custodians/me/alerts/webhooks/{alertWebhookId}/delivery-events/{deliveryEventId}/retries`](/api-reference/alert-webhooks/retry-an-alert-webhook-delivery-event). See [Delivery event lifecycle](#delivery-event-lifecycle) for details on how manual retries interact with the automatic retry loop.
</Warning>

**Headers:**

| Name               | Type   | Description                                                                                                                                                   |
| ------------------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Content-Type`     | String | `application/json`                                                                                                                                            |
| `X-WEBHOOK-SECRET` | String | You can find the alert webhook secret in the [Portal Admin Dashboard](https://app.portalhq.io) on the `"Webhooks"` page under the `"Alert Webhooks"` section. |

**Request body (examples)**

```json theme={null}
{
  "data": {
    "rawEvents": [
      {
        "fee": 10000,
        "slot": 438851202,
        "type": "REVOKE",
        "events": {},
        "source": "SOLANA_PROGRAM_LIBRARY",
        "feePayer": "98aauPkabddWQVyHWNw5LJLDb84ETKfN2xYUy1Zr2sQy",
        "signature": "3p4qVucEtUhZ3GbsQv3ZQS2mTRsziSgJs6B7gre34X2xnG2WPkaNvHuk9wDRJ2kKzKa8M7Rmykpr6wTtYLRuY9oh",
        "timestamp": 1769837914,
        "accountData": [
          {
            "account": "98aauPkabddWQVyHWNw5LJLDb84ETKfN2xYUy1Zr2sQy",
            "nativeBalanceChange": -10000,
            "tokenBalanceChanges": []
          },
          {
            "account": "J4AVMHNUkxhjUmWg25RTfysAEx7FnRYymT2xZkc12sKK",
            "nativeBalanceChange": 0,
            "tokenBalanceChanges": []
          },
          {
            "account": "pQxS4DANX5uMkDkzJSk2dSBnrEWZ1enUL2XkKF6Nhbh",
            "nativeBalanceChange": 0,
            "tokenBalanceChanges": []
          },
          {
            "account": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
            "nativeBalanceChange": 0,
            "tokenBalanceChanges": []
          }
        ],
        "description": "",
        "instructions": [
          {
            "data": "6",
            "accounts": [
              "pQxS4DANX5uMkDkzJSk2dSBnrEWZ1enUL2XkKF6Nhbh",
              "J4AVMHNUkxhjUmWg25RTfysAEx7FnRYymT2xZkc12sKK"
            ],
            "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
            "innerInstructions": []
          }
        ],
        "tokenTransfers": [],
        "nativeTransfers": [],
        "transactionError": null
      }
    ]
  },
  "metadata": {
    "isDevnet": true,
    "eventTypes": ["REVOKE"],
    "environmentId": "environmentId"
  },
  "type": "SOLANA_REVOKE_V1"
}
```

## Security

Here are a few considerations to ensure your alert webhooks are implemented securely:

* Alert webhook URLs must use `HTTPS`.
* Verify that each alert webhook request has the expected `X-WEBHOOK-SECRET` header value. You can find the secret for your alert webhook in the [Portal Admin Dashboard](https://app.portalhq.io).
* Restricting requests on your alert webhook server to only those from Portal's IP addresses protects against requests from other parties. Configure your alert webhook server to only accept inbound connections from our IP addresses. Portal always makes requests from the IP addresses `35.203.150.117`, `104.155.171.139` or `35.185.20.23`.

## Example Implementation

We provide a [reference implementation](https://github.com/portal-hq/portal-alert-webhooks-example) of alert webhooks using TypeScript and Express. This example demonstrates best practices for handling alert webhook events, including:

* 🔒 IP address verification
* 🔑 Webhook secret validation
* ⚡ Async event processing

You can use this as a starting point for your own implementation:

```bash theme={null}
git clone https://github.com/portal-hq/portal-alert-webhooks-example
cd portal-alert-webhooks-example
npm install
```

## FAQ

### What if I miss an alert webhook event?

* If your alert webhook is down or is not responding with `2xx` status codes, Portal will retry sending the alert webhook event using an exponential backoff with up to 9 retries. See the [retry schedule](#retry-schedule) for details.
* You can inspect and re-drive delivery attempts using the new delivery-event endpoints:
  * List all of your alert webhooks with [`GET /custodians/me/alerts/webhooks`](/api-reference/alert-webhooks/list-alert-webhooks).
  * List the delivery events for a specific alert webhook with [`GET /custodians/me/alerts/webhooks/{alertWebhookId}/delivery-events`](/api-reference/alert-webhooks/list-alert-webhook-delivery-events). Filter by `statuses=FAILED` to find events that need attention, or by `statuses=PENDING` to see events still waiting on the automatic retry loop.
  * Re-drive a single delivery event with [`POST /custodians/me/alerts/webhooks/{alertWebhookId}/delivery-events/{deliveryEventId}/retries`](/api-reference/alert-webhooks/retry-an-alert-webhook-delivery-event). This works on `FAILED` events and also on `PENDING` events — but note that retrying a `PENDING` event removes it from the automatic retry loop. See [Delivery event lifecycle](#delivery-event-lifecycle) for the full state machine.

### Should I process webhook events before responding?

No. Please **acknowledge webhooks as quickly as possible**. If you need to process the alert webhook event you receive, process it *after* responding to Portal with a `2xx` status code. (We only wait up to 10 seconds to receive a response before considering the alert webhook event's delivery as `failed`.)

### Why am I receiving two events with the same `from` and `to` for the same transaction?

This is expected when **both** the sender and the receiver are addresses you've registered with Portal (e.g. two of your Portal clients, or a Portal client and one of your [external addresses](#external-addresses)). A single on-chain transfer produces an `OUTBOUND` event from the sender's perspective and an `INBOUND` event from the receiver's perspective.

Both events describe the same on-chain movement, so the `from` and `to` fields are identical — those fields reflect the actual on-chain direction (who sent, who received), not the perspective of the subscribed wallet. Use the `direction` and `triggeredBy` fields to distinguish the two. For EVM events, use `data[].metadata.triggeredBy`; for Solana events, use `data.rawEvents[].triggeredBy`. Correlate the pair by `transactionHash` + `blockNumber` (EVM) or `signature` (Solana).

See [Dual events for internal transfers](#dual-events-for-internal-transfers) for full details and example payloads.

## Support

And that's it! 🎉 You've now implemented alert webhooks and are receiving realtime wallet notifications for your Portal clients! As always, please reach out to our team if you have any questions.
