Skip to main content
Wallet transfer webhooks allow you to receive real-time notifications when tokens are transferred in or out of wallets in your project. This enables you to track transfers, update your application state, and trigger automated workflows based on transfer events.

Event Types

Crossmint provides two webhook event types for wallet transfers:

wallets.transfer.in

Triggered when tokens are transferred into a wallet in your project. This event is only sent for successful transfers that have been confirmed on-chain.

wallets.transfer.out

Triggered when tokens are transferred out of a wallet in your project. This event is sent for both successful and failed transfer attempts, allowing you to track the complete lifecycle of outgoing transactions.

Setup

To start receiving wallet transfer webhooks, you need to configure a webhook endpoint in the Crossmint Console.

Step 1: Add a Webhook Endpoint

Follow the instructions in Add an Endpoint to configure your webhook URL and select the event types you want to receive. When adding your endpoint, make sure to select:
  • wallets.transfer.in - to receive notifications for incoming transfers
  • wallets.transfer.out - to receive notifications for outgoing transfers
Your webhook endpoint must return a 2xx HTTP status code within 15 seconds to acknowledge receipt. This is critical for webhook delivery confirmation.

Step 2: Verify Webhook Signatures

To ensure webhook requests are legitimate and come from Crossmint, you must verify the signature of each webhook. See Verify Webhooks for detailed instructions.

Event Schemas

Both wallets.transfer.in and wallets.transfer.out events share a common structure with some key differences based on the transfer direction and outcome.

Common Fields

All wallet transfer events include the following fields:
FieldTypeDescription
idstringUnique identifier for the webhook event
typestringEvent type: "wallets.transfer.in" or "wallets.transfer.out"
data.senderobjectInformation about the sender wallet
data.sender.addressstringBlockchain address of the sender
data.sender.chainstringChain identifier (e.g., "ethereum", "polygon", "solana")
data.sender.locatorstringWallet locator in the format chain:address
data.sender.ownerstring (optional)Owner identifier if the sender is a Crossmint-managed wallet
data.recipientobjectInformation about the recipient wallet
data.recipient.addressstringBlockchain address of the recipient
data.recipient.chainstringChain identifier
data.recipient.locatorstringWallet locator in the format chain:address
data.recipient.ownerstring (optional)Owner identifier for the recipient wallet
data.tokenobjectInformation about the transferred token
data.token.typestringToken type, currently only "fungible" is supported
data.token.chainstringChain where the token exists
data.token.locatorstringToken locator in the format chain:contractAddress
data.token.amountstringHuman-readable amount (adjusted for decimals)
data.token.rawAmountstringRaw amount in smallest unit (e.g., wei for ETH)
data.token.contractAddressstring (EVM)Token contract address for EVM chains
data.token.mintHashstring (Solana)Token mint address for Solana
data.token.contractIdstring (Stellar)Token contract ID for Stellar
data.token.decimalsnumberNumber of decimals for the token
data.token.symbolstring (optional)Token symbol (e.g., "USDC", "ETH")
data.statusstringTransfer status: "succeeded" or "failed"
data.completedAtstringISO 8601 timestamp when the transfer was completed

Type-Specific Differences

Incoming Transfers (wallets.transfer.in):
  • data.status is always "succeeded" (only successful transfers are reported)
  • data.onChain is always present with transaction details
Outgoing Transfers (wallets.transfer.out):
  • data.status can be "succeeded" or "failed"
  • data.onChain is present only when status is "succeeded"
  • data.error is present only when status is "failed"

Event Examples

{
  "id": "whevnt_12324",
  "type": "wallets.transfer.in",
  "data": {
    "sender": {
      "address": "0x1234567890123456789012345678901234567890",
      "chain": "ethereum",
      "locator": "ethereum:0x1234567890123456789012345678901234567890"
    },
    "recipient": {
      "address": "0x0987654321098765432109876543210987654321",
      "chain": "ethereum",
      "locator": "ethereum:0x0987654321098765432109876543210987654321",
      "owner": "email:user@example.com"
    },
    "token": {
      "type": "fungible",
      "chain": "ethereum",
      "locator": "ethereum:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
      "amount": "100.50",
      "rawAmount": "100500000",
      "contractAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
      "decimals": 6,
      "symbol": "USDC"
    },
    "status": "succeeded",
    "completedAt": "2025-10-21T12:34:56.789Z",
    "onChain": {
      "txId": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
      "explorerLink": "https://etherscan.io/tx/0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
    }
  }
}
{
  "id": "whevnt_56789",
  "type": "wallets.transfer.out",
  "data": {
    "sender": {
      "address": "0x0987654321098765432109876543210987654321",
      "chain": "ethereum",
      "locator": "ethereum:0x0987654321098765432109876543210987654321",
      "owner": "email:user@example.com"
    },
    "recipient": {
      "address": "0x1234567890123456789012345678901234567890",
      "chain": "ethereum",
      "locator": "ethereum:0x1234567890123456789012345678901234567890"
    },
    "token": {
      "type": "fungible",
      "chain": "ethereum",
      "locator": "ethereum:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
      "amount": "50.25",
      "rawAmount": "50250000",
      "contractAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
      "decimals": 6,
      "symbol": "USDC"
    },
    "status": "succeeded",
    "completedAt": "2025-10-21T12:34:56.789Z",
    "onChain": {
      "txId": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
      "explorerLink": "https://etherscan.io/tx/0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
    }
  }
}
{
  "id": "whevnt_99999",
  "type": "wallets.transfer.out",
  "data": {
    "sender": {
      "address": "0x0987654321098765432109876543210987654321",
      "chain": "ethereum",
      "locator": "ethereum:0x0987654321098765432109876543210987654321",
      "owner": "email:user@example.com"
    },
    "recipient": {
      "address": "0x1234567890123456789012345678901234567890",
      "chain": "ethereum",
      "locator": "ethereum:0x1234567890123456789012345678901234567890"
    },
    "token": {
      "type": "fungible",
      "chain": "ethereum",
      "locator": "ethereum:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
      "amount": "1000.00",
      "rawAmount": "1000000000",
      "contractAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
      "decimals": 6,
      "symbol": "USDC"
    },
    "status": "failed",
    "completedAt": "2025-10-21T12:34:56.789Z",
    "error": {
      "message": "Transaction reverted",
      "reason": "execution_reverted",
      "revert": {
        "type": "contract_call",
        "reason": "ERC20: transfer amount exceeds balance"
      }
    }
  }
}

Error Fields (Outgoing Transfers Only)

When an outgoing transfer fails, the following error fields are included:
FieldTypeDescription
data.error.messagestringError message
data.error.reasonstringError reason (e.g., "execution_reverted", "program_error", "build_failed")
data.error.revertobjectRevert details from smart contract
data.error.revert.typestringRevert type: "contract_call", "wallet_authorization", or "wallet_deployment"
data.error.revert.reasonstringRevert reason message
data.error.logsany (optional)Additional error logs

Best Practices

For comprehensive guidance on implementing reliable webhook handlers, see Webhook Best Practices. This includes detailed information on: