> ## 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.

# Migrating from legacy Solana alert webhook events

> Upgrade from the legacy Solana alert webhook events to the current ones to get per-address delivery, the `triggeredBy` field, and new properties.

The current Solana alert webhook events supersede the legacy ones. They change the delivery model from "one event per transaction" to "one event per address involved", and introduce a new event type for delegated transfers along with new payload fields. This guide is for customers still consuming the legacy Solana alert webhook events who want to migrate.

## What's changing

* **Per-address delivery.** The legacy webhook sent a single event per Solana transaction. The new webhook sends **one event per subscribed address involved** in the transaction. If two of your Portal clients are involved in a single Solana transaction (e.g. one sends SOL to the other), you will now receive **two** webhook events for that transaction — each with a different `triggeredBy` value and the same `signature`.

* **New event types.** Each legacy event type has a replacement, plus a brand-new event for delegated transfers:

  * **`SOLANA_TX_V2`** — Solana native (SOL) transfers and SPL token transfers. *(Replaces `SOLANA_TX_V1`, without delegated transfers.)*
  * **`SOLANA_APPROVE_V2`** — Solana delegation approve transactions. *(Replaces `SOLANA_APPROVAL_V1`.)*
  * **`SOLANA_REVOKE_V2`** — Solana delegation revoke transactions. *(Replaces `SOLANA_REVOKE_V1`.)*
  * **`SOLANA_DELEGATED_TRANSFER_V2`** — **New event.** Fires when a delegate executes a transfer using funds from another user's previously-approved delegation.

  <Warning>
    If you don't subscribe to `SOLANA_DELEGATED_TRANSFER_V2`, you will not be notified when a delegate moves tokens that one of your addresses had previously approved. To preserve parity with the legacy "every transfer involving my addresses" behavior, subscribe to **both** `SOLANA_TX_V2` and `SOLANA_DELEGATED_TRANSFER_V2`.
  </Warning>

* **`triggeredBy` field.** Every event payload now includes a `triggeredBy` field. It identifies **which of your subscribed addresses** caused this specific event delivery. Use this to route events to the right user/account on your side without re-deriving it from the raw transaction data.

* **`direction` field.** Every event payload also includes a `direction` field that describes what the `triggeredBy` address did in the transaction:

  | 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_TRANSFER_V2` 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). |

* **`recentBlockhash` field.** Every event payload now includes a `recentBlockhash` field — the base58-encoded `recentBlockhash` of the transaction.

## Payload differences

The same Solana transfer (a `0.01 SOL` receive) shows up like this on the legacy webhook vs the new one. Note the new `triggeredBy` and `direction` fields, and the `type` value changing from `SOLANA_TX_V1` to `SOLANA_TX_V2`.

<Tabs>
  <Tab title="Legacy (V1) payload">
    ```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": []
              }
            ],
            "description": "75ZfLXXsSpycDvHTQuHnGQuYgd2ihb6Bu4viiCCQ7P4H transferred 0.01 SOL to 67vPQk4SMRRmZdcfyRd6kBggyeca6TqWmsAkJatNDHyx.",
            "tokenTransfers": [],
            "nativeTransfers": [
              {
                "amount": 10000000,
                "toUserAccount": "67vPQk4SMRRmZdcfyRd6kBggyeca6TqWmsAkJatNDHyx",
                "fromUserAccount": "75ZfLXXsSpycDvHTQuHnGQuYgd2ihb6Bu4viiCCQ7P4H"
              }
            ],
            "transactionError": null
          }
        ]
      },
      "metadata": {
        "isDevnet": true,
        "eventTypes": ["TRANSFER"],
        "environmentId": "environmentId"
      },
      "type": "SOLANA_TX_V1"
    }
    ```
  </Tab>

  <Tab title="Current (V2) payload">
    ```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": []
              }
            ],
            "description": "75ZfLXXsSpycDvHTQuHnGQuYgd2ihb6Bu4viiCCQ7P4H transferred 0.01 SOL to 67vPQk4SMRRmZdcfyRd6kBggyeca6TqWmsAkJatNDHyx.",
            "tokenTransfers": [],
            "nativeTransfers": [
              {
                "amount": 10000000,
                "toUserAccount": "67vPQk4SMRRmZdcfyRd6kBggyeca6TqWmsAkJatNDHyx",
                "fromUserAccount": "75ZfLXXsSpycDvHTQuHnGQuYgd2ihb6Bu4viiCCQ7P4H"
              }
            ],
            "recentBlockhash": "2d7rAqbS4ZFsroMHkxA8kmJdvmoPy4mtZRqCpSNTa1DQ",
            "transactionError": null
          }
        ]
      },
      "metadata": {
        "isDevnet": true,
        "eventTypes": ["TRANSFER"],
        "environmentId": "environmentId"
      },
      "type": "SOLANA_TX_V2"
    }
    ```
  </Tab>
</Tabs>

## Event payloads

Below is a full example payload for each new event type so you can wire up parsers and types ahead of cutting over.

### `SOLANA_TX_V2`

```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"
}
```

### `SOLANA_APPROVE_V2`

```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_REVOKE_V2`

```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_TRANSFER_V2`

```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"
}
```

## Migration steps

1. **Expect multiple events per transaction.** If two or more of your subscribed addresses are involved in the same Solana transaction, you will receive **one event per address**. Update your event handlers to be safe under this fan-out.

   * **Idempotency between transactions:** dedupe on the `signature` field. Solana transactions are uniquely identified by their `signature`, so it works as a stable idempotency key for "have I seen this transaction before?".
   * **Idempotency per (transaction, address):** dedupe on the composite key `signature` + `triggeredBy`. Use this when you process events per-address, so each `(transaction, address)` pair is processed exactly once even if the webhook is retried.

2. **Subscribe to the events you need from the Portal Admin Dashboard.** In the **Alert Webhooks** settings, select the event types your integration cares about: `SOLANA_TX_V2`, `SOLANA_APPROVE_V2`, `SOLANA_REVOKE_V2`, and/or `SOLANA_DELEGATED_TRANSFER_V2`.

   <Note>
     If you want to keep parity with the legacy webhook — i.e. be notified about **every** transfer involving your addresses — subscribe to **both** `SOLANA_TX_V2` and `SOLANA_DELEGATED_TRANSFER_V2`. Without `SOLANA_DELEGATED_TRANSFER_V2`, transfers executed by a delegate against an approval that one of your addresses granted will not generate any webhook event.
   </Note>

3. **Test that events are being received with the correct types.** Send a test transaction (devnet works well) and confirm:
   * You receive an event whose `type` ends in `_V2` (not `_V1`).
   * The payload contains both `triggeredBy` and `direction`.
   * When two of your subscribed addresses are involved in the same transaction, you receive **two** events with different `triggeredBy` values and a shared `signature`.
