Skip to main content

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.

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

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.
  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:
{
  "address": "0x1234...",
  "namespace": "eip155"
}
For Solana addresses, use "solana" as the namespace:
{
  "address": "ABC123...",
  "namespace": "solana"
}
Addresses are validated for correct format based on the selected namespace. Blackhole addresses (such as null or dead addresses) are rejected.

Types of alerts

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.
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)
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.
Retries: When your webhook does not respond to a request, or if you do not respond with a 2XX status code, Portal will attempt to retry the request in intervals (1 minute, 10 minutes, 1 hour, 2 hours, 6 hours, 12 hours, and at 24 hours). If no successful response occurs during that timeframe, the webhook event will be dropped and you will need to manually replay the event.
See the table below for all EVM chains we support for EVM Wallet Transactions alerts.
NameChain IDBlocks until confirmed
Ethereumeip155:112
Ethereum Sepoliaeip155:1115511118
Polygoneip155:137100
Polygon Amoyeip155:80002100
Arbitrumeip155:4216118
Arbitrum Sepoliaeip155:421614600
Optimismeip155:10500
Optimism Sepoliaeip155:11155420600
Baseeip155:8453100
Base Sepoliaeip155:84532100
Headers:
NameTypeDescription
Content-TypeStringapplication/json
X-WEBHOOK-SECRETStringYou can find the alert webhook secret in the Portal Admin Dashboard on the "Webhooks" page under the "Alert Webhooks" section.
Request body (examples)
Unconfirmed alert:
{
  "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:
{
  "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"
}
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.

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.
Still on the legacy Solana alert webhook events? See Solana Wallet Transactions (Legacy) and the migration guide.

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.
ValueMeaning
OUTBOUNDtriggeredBy 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.
INBOUNDtriggeredBy is the receiver of the transaction — the one who received tokens, or who was granted approval to spend delegated tokens.
DELEGATEDOnly 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).
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 alert webhook events support up to 1,000,000 addresses. Contact our team if you require additional capacity.
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.
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. If no successful response occurs after all retries, the webhook event will be dropped.

Retry schedule

RetryBackoff
14 minutes
28 minutes
315 minutes
430 minutes
51 hour
62 hours
74 hours
88 hours
916 hours
Headers:
NameTypeDescription
Content-TypeStringapplication/json
X-WEBHOOK-SECRETStringYou can find the alert webhook secret in the Portal Admin Dashboard on the "Webhooks" page under the "Alert Webhooks" section.
Request body (examples)
{
  "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"
          }
        ],
        "transactionError": null
      }
    ]
  },
  "metadata": {
    "isDevnet": true,
    "eventTypes": ["TRANSFER"],
    "environmentId": "environmentId"
  },
  "type": "SOLANA_TX_V2"
}

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 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.
Retries: Solana alert webhook events use exponential backoff with up to 9 retries. See the retry schedule for details.
Headers:
NameTypeDescription
Content-TypeStringapplication/json
X-WEBHOOK-SECRETStringYou can find the alert webhook secret in the Portal Admin Dashboard on the "Webhooks" page under the "Alert Webhooks" section.
Request body (examples)
{
  "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": [],
        "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 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.
Retries: Solana alert webhook events use exponential backoff with up to 9 retries. See the retry schedule for details.
Headers:
NameTypeDescription
Content-TypeStringapplication/json
X-WEBHOOK-SECRETStringYou can find the alert webhook secret in the Portal Admin Dashboard on the "Webhooks" page under the "Alert Webhooks" section.
Request body (examples)
{
  "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": [],
        "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 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.
Retries: Solana alert webhook events use exponential backoff with up to 9 retries. See the retry schedule for details.
Headers:
NameTypeDescription
Content-TypeStringapplication/json
X-WEBHOOK-SECRETStringYou can find the alert webhook secret in the Portal Admin Dashboard on the "Webhooks" page under the "Alert Webhooks" section.
Request body (examples)
{
  "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": [],
        "transactionError": null
      }
    ]
  },
  "metadata": {
    "isDevnet": false,
    "eventTypes": ["DELEGATED_TRANSFER"],
    "environmentId": "environmentId"
  },
  "type": "SOLANA_DELEGATED_TRANSFER_V2"
}

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. Regardless of your API response, the eject operation has already been processed for the Portal client who ejected their wallet. Headers:
NameTypeDescription
Content-TypeStringapplication/json
X-WEBHOOK-SECRETStringYou can find the alert webhook secret in the Portal Admin Dashboard on the "Webhooks" page under the "Alert Webhooks" section.
Request body (examples):
{
  "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.
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.
Headers:
NameTypeDescription
Content-TypeStringapplication/json
X-WEBHOOK-SECRETStringYou can find the alert webhook secret in the Portal Admin Dashboard on the "Webhooks" page under the "Alert Webhooks" section.
Request body (examples):
{
  "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"
}

Solana Alert Webhook Events (Legacy)

Already integrated against the V1 events below? Follow the migration guide for legacy Solana alert webhook events to move to the current alerts and to subscribe to the new Solana Delegated Transfers event.

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 for the cutover steps.
V1V2
Delivery modelOne event per Solana transactionOne event per subscribed address involved in the transaction
Address identificationNo triggeredBy fieldIncludes triggeredBy — the address that triggered the event
Direction parsingNo direction fieldIncludes direction (OUTBOUND, INBOUND, or DELEGATED)
Delegated transfersNot delivered as a separate eventNew Solana Delegated Transfers event when a delegate spends an approved allowance
Event typeSOLANA_TX_V1SOLANA_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)

Deprecation notice: Solana Wallet Transactions (V1) will be deprecated in the near future. We recommend migrating from the legacy Solana alert webhook events 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.
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.
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
Solana alert webhook events support up to 100,000 addresses. Contact our team if you require additional capacity.
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.
Retries: When your webhook does not respond to a request, or if you do not respond with a 2XX status code, Portal will attempt to retry the request once per minute for 3 minutes. If no successful response occurs during that timeframe, the webhook event will be dropped.
Headers:
NameTypeDescription
Content-TypeStringapplication/json
X-WEBHOOK-SECRETStringYou can find the alert webhook secret in the Portal Admin Dashboard on the "Webhooks" page under the "Alert Webhooks" section.
Request body (examples)
{
  "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"
}

Solana Token Approvals (Legacy)

Deprecation notice: Solana Token Approvals (V1) will be deprecated in the near future. We recommend migrating from the legacy Solana alert webhook events as soon as possible.
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.
Retries: When your webhook does not respond to a request, or if you do not respond with a 2XX status code, Portal will attempt to retry the request once per minute for 3 minutes. If no successful response occurs during that timeframe, the webhook event will be dropped.
Headers:
NameTypeDescription
Content-TypeStringapplication/json
X-WEBHOOK-SECRETStringYou can find the alert webhook secret in the Portal Admin Dashboard on the "Webhooks" page under the "Alert Webhooks" section.
Request body (examples)
{
  "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)

Deprecation notice: Solana Token Revocations (V1) will be deprecated in the near future. We recommend migrating from the legacy Solana alert webhook events as soon as possible.
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.
Retries: When your webhook does not respond to a request, or if you do not respond with a 2XX status code, Portal will attempt to retry the request once per minute for 3 minutes. If no successful response occurs during that timeframe, the webhook event will be dropped.
Headers:
NameTypeDescription
Content-TypeStringapplication/json
X-WEBHOOK-SECRETStringYou can find the alert webhook secret in the Portal Admin Dashboard on the "Webhooks" page under the "Alert Webhooks" section.
Request body (examples)
{
  "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.
  • 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 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:
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.
    • For Solana alert webhook events V1, we retry once every minute for 3 minutes.
    • For Solana alert webhook events V2, we retry using an exponential backoff with up to 9 retries. See the retry schedule for details.
    • For EIP-155 webhook events, we retry 6 times progressively over 24 hours.
  • If your EIP-155 alert webhook fails to receive the event:
    • You can find all of your alert webhooks using this endpoint.
    • You can then find an alert webhook’s associated events using this endpoint.
    • You can replay the exact alert webhook event that failed to be delivered using this endpoint.

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

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.