Custodian
Webhook

Webhook

Register an HTTPS endpoint to receive real-time events such as account creation, paid invoices, wallet deposits, withdrawals, and invoice lifecycle changes. Settings endpoints require authentication.

Manage Webhook Settings

Get Settings

GET /api/v1/webhooks/settings

{
  "data": {
    "url": "https://your-app.com/webhook",
    "enabled": true,
    "updatedAt": "2026-01-01T00:00:00Z"
  }
}

Set / Update Settings

PUT /api/v1/webhooks/settings

FieldTypeRequiredDescription
urlstringYesA valid https URL
{ "url": "https://your-app.com/webhook" }

Delete Settings

DELETE /api/v1/webhooks/settings{ "status": "ok" }

Send a Test Event

POST /api/v1/webhooks/test (no body). Queues a webhook.test delivery to your URL.

List Deliveries

GET /api/v1/webhooks/deliveries — query page (default 1), limit (default 20, max 100).

Receiving Events

Xellar sends a POST request to your configured URL with these headers:

HeaderDescription
Content-Typeapplication/json
X-Xellar-Event-IdUnique event ID (same value as the JSON id field)
X-Xellar-TimestampUnix time in seconds
X-Xellar-SignatureBase64 ED25519 signature

Payload Envelope

Every event uses the same top-level shape:

{
  "apiVersion": "2025-06-22",
  "id": "<event-id>",
  "type": "<event-type>",
  "createdAt": "2026-07-08T06:21:44.051787576Z",
  "data": { }
}
FieldDescription
apiVersionWebhook schema version. Currently 2025-06-22.
idStable event identifier used for idempotency and signature verification.
typeEvent type string (see below).
createdAtUTC timestamp when the event was created.
dataEvent-specific payload.

Event ID Format

The id field (and X-Xellar-Event-Id header) follow a predictable pattern per event type:

TypeEvent ID pattern
account.createdaccount.created:<accountId>
deposit.invoice.paiddeposit.invoice.paid:<invoiceId>:<txHash>
deposit.wallet.crediteddeposit.wallet.credited:<tokenTransferId>
invoice.expiredinvoice.expired:<invoiceId>
invoice.failedinvoice.failed:<invoiceId>:<txHash>
withdraw.createdwithdraw.created:<withdrawId>
withdraw.successwithdraw.success:<withdrawId>
withdraw.failedwithdraw.failed:<withdrawId>
webhook.testRandom UUID

Event Types Overview

TypeWhen it fires
account.createdA custodial account is provisioned and wallet addresses are ready
deposit.invoice.paidAn invoice receives the exact expected payment
deposit.wallet.creditedA subscribed wallet receives a deposit (not matched to an invoice)
invoice.expiredAn invoice passes its expiry time without payment
invoice.failedA payment is detected for an invoice address but the amount does not match
withdraw.createdA withdrawal request is accepted and queued
withdraw.successA withdrawal completes on-chain
withdraw.failedA withdrawal fails
webhook.testYou call POST /api/v1/webhooks/test

Shared Objects

Several fields appear across multiple event types:

token

FieldTypeDescription
idstringToken UUID
tokenCodestringToken code (e.g. usdc-polygon-pos)
symbolstringDisplay symbol (e.g. USDT)
decimalsnumberToken decimal places

transaction (on-chain transfer details)

FieldTypeDescription
txHashstringTransaction hash
chainIdnumberChain ID
networkCodestringNetwork code (e.g. polygon-pos)
fromstringSender address
tostringRecipient address
tokenAddressstringContract address (native coin may be empty)
tokenSymbolstringToken symbol from the chain event
valuestringRaw amount in smallest unit (string)
valueDecimalsnumberDecimals used for value
blockchainTypestringe.g. evm, tron, solana

credit (ledger credit applied to your balance)

FieldTypeDescription
tokenCodestringToken credited
amountnumberHuman-readable amount
referenceTypestringinvoice or token_transfer
referenceIdstringInvoice ID or token transfer ID

account.created

Sent when MPC account creation completes and wallet addresses are available for all configured networks.

data fields

FieldTypeDescription
appIdstringYour app UUID
accountobjectCreated account details
walletsarrayOne entry per network wallet

account object

FieldTypeDescription
idstringAccount UUID
subIdstringYour user identifier
mpcSubIdstringMPC sub-account ID
statusstringactive
poolTypestringdedicated or organization_owned

wallets[] object

FieldTypeDescription
walletIdstringWallet UUID
networkstringNetwork code
addressstringDeposit address on that network
addressTypestringe.g. legacy, native-segwit, or empty for EVM/Solana/Tron
{
  "apiVersion": "2025-06-22",
  "id": "account.created:a9e73399-00ce-4c1f-bd25-316c140345a6",
  "type": "account.created",
  "createdAt": "2026-07-08T06:10:26.462722174Z",
  "data": {
    "appId": "4f83980c-c764-4b5d-9c0c-82e301ad6e45",
    "account": {
      "id": "a9e73399-00ce-4c1f-bd25-316c140345a6",
      "subId": "baru1",
      "status": "active",
      "mpcSubId": "4f83980c-c764-4b5d-9c0c-82e301ad6e45-baru1",
      "poolType": "dedicated"
    },
    "wallets": [
      {
        "walletId": "97736085-85be-405e-9b50-be3d1feec524",
        "network": "ethereum",
        "address": "0xb22139675b58b1e5ae1541530aada62afa309077",
        "addressType": ""
      },
      {
        "walletId": "57e1151f-26ef-41e9-ae12-caa0748be294",
        "network": "polygon-pos",
        "address": "0xb22139675b58b1e5ae1541530aada62afa309077",
        "addressType": ""
      },
      {
        "walletId": "61b86a18-ec2e-4179-ad32-c39877860eb9",
        "network": "bitcoin",
        "address": "1MYp8aU3qNwhtU5dHrrSPkZYNAqug9bq15",
        "addressType": "legacy"
      }
    ]
  }
}

The wallets array includes every network configured for your app. EVM networks typically share the same address; UTXO chains may include multiple address types per network.


deposit.invoice.paid

Sent when an incoming transfer exactly matches a pending invoice amount.

data fields

FieldTypeDescription
matchTypestringAlways invoice
appIdstringYour app UUID
userIdstringEnd-user UUID linked to the invoice
invoiceobjectPaid invoice details
transactionobjectOn-chain transfer
tokenobjectToken metadata
tokenTransferobjectInternal transfer record
creditobjectBalance credit applied

invoice object

FieldTypeDescription
idstringInvoice UUID
statusstringpaid
amountnumberExpected amount
amountInDecimalsstringAmount in smallest unit
referencestringYour invoice reference
walletIdstringDeposit wallet UUID
depositAddressstringAddress that received payment
paidAtstringUTC timestamp
transactionHashstringMatching transaction hash
{
  "apiVersion": "2025-06-22",
  "id": "deposit.invoice.paid:9544b2ae-c84a-4bc5-a7ed-599b20d0f25d:0x005c514230821be3b7f0a588b9012f76cf1b84a90a28235cf772128ec1775b05",
  "type": "deposit.invoice.paid",
  "createdAt": "2026-07-08T06:21:44.051787576Z",
  "data": {
    "appId": "4f83980c-c764-4b5d-9c0c-82e301ad6e45",
    "matchType": "invoice",
    "userId": "de4865aa-f81b-4fe0-9e62-6e5a8f674280",
    "invoice": {
      "id": "9544b2ae-c84a-4bc5-a7ed-599b20d0f25d",
      "status": "paid",
      "amount": 0.00123,
      "amountInDecimals": "1230",
      "reference": "INV-001990",
      "walletId": "5326036c-50c3-4d63-8150-69cb253542e3",
      "depositAddress": "0x37937c09c7454b1d4ff8fb7657cfd84418168036",
      "paidAt": "2026-07-08T06:21:44.041555Z",
      "transactionHash": "0x005c514230821be3b7f0a588b9012f76cf1b84a90a28235cf772128ec1775b05"
    },
    "transaction": {
      "txHash": "0x005c514230821be3b7f0a588b9012f76cf1b84a90a28235cf772128ec1775b05",
      "chainId": 137,
      "networkCode": "polygon-pos",
      "from": "0x0f6c4A746C303a2ad7E008302d61f615D97dfe3e",
      "to": "0x37937c09C7454B1d4Ff8fB7657cFd84418168036",
      "tokenAddress": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
      "value": "1230",
      "valueDecimals": 6,
      "blockchainType": "evm"
    },
    "token": {
      "id": "53c22951-9740-47dc-82f0-db0179e617a7",
      "tokenCode": "usdc-polygon-pos",
      "symbol": "USDT",
      "decimals": 6
    },
    "tokenTransfer": {
      "id": "9633bb86-aeec-4b83-97c3-9d9c1ff48de1",
      "amountInDecimals": "1230"
    },
    "credit": {
      "tokenCode": "usdc-polygon-pos",
      "amount": 0.00123,
      "referenceType": "invoice",
      "referenceId": "9544b2ae-c84a-4bc5-a7ed-599b20d0f25d"
    }
  }
}

deposit.wallet.credited

Sent when a deposit is credited to a listen wallet and is not matched to an invoice.

data fields

FieldTypeDescription
matchTypestringAlways wallet
appIdstringYour app UUID
walletobjectCredited wallet
transactionobjectOn-chain transfer
tokenobjectToken metadata
tokenTransferobjectInternal transfer record
creditobjectBalance credit applied

wallet object

FieldTypeDescription
walletIdstringWallet UUID
addressstringWallet address
networkCodestringNetwork code
chainIdnumberChain ID
{
  "apiVersion": "2025-06-22",
  "id": "deposit.wallet.credited:44f5465c-e1b4-4128-aa88-728290a9dea6",
  "type": "deposit.wallet.credited",
  "createdAt": "2026-07-08T06:24:38.863991778Z",
  "data": {
    "appId": "4f83980c-c764-4b5d-9c0c-82e301ad6e45",
    "matchType": "wallet",
    "wallet": {
      "walletId": "57e1151f-26ef-41e9-ae12-caa0748be294",
      "address": "0xb22139675b58b1e5ae1541530aada62afa309077",
      "networkCode": "polygon-pos",
      "chainId": 137
    },
    "transaction": {
      "txHash": "0x79ed73ec782a3a01050885b37a7261873369448e6d863be23664f96a40f305fb",
      "chainId": 137,
      "networkCode": "polygon-pos",
      "from": "0x0f6c4A746C303a2ad7E008302d61f615D97dfe3e",
      "to": "0xB22139675b58B1E5aE1541530AAdA62AFA309077",
      "tokenAddress": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
      "value": "1340",
      "valueDecimals": 6,
      "blockchainType": "evm"
    },
    "token": {
      "id": "53c22951-9740-47dc-82f0-db0179e617a7",
      "tokenCode": "usdc-polygon-pos",
      "symbol": "USDT",
      "decimals": 6
    },
    "tokenTransfer": {
      "id": "44f5465c-e1b4-4128-aa88-728290a9dea6",
      "amountInDecimals": "1340"
    },
    "credit": {
      "tokenCode": "usdc-polygon-pos",
      "amount": 0.00134,
      "referenceType": "token_transfer",
      "referenceId": "44f5465c-e1b4-4128-aa88-728290a9dea6"
    }
  }
}

invoice.expired

Sent when a pending invoice passes its expiry window without receiving payment.

data fields

FieldTypeDescription
appIdstringYour app UUID
userIdstringEnd-user UUID
invoiceobjectExpired invoice details
tokenobjectToken metadata

invoice object

FieldTypeDescription
idstringInvoice UUID
statusstringexpired
amountnumberExpected amount
amountInDecimalsstringAmount in smallest unit
referencestringYour invoice reference
walletIdstringDeposit wallet UUID
depositAddressstringAddress that was assigned
expiresAtstringOriginal expiry timestamp
bufferInSecondsnumberGrace buffer after expiry
expiredAtstringWhen the expiry was processed
{
  "apiVersion": "2025-06-22",
  "id": "invoice.expired:9544b2ae-c84a-4bc5-a7ed-599b20d0f25d",
  "type": "invoice.expired",
  "createdAt": "2026-07-08T07:00:00.000000000Z",
  "data": {
    "appId": "4f83980c-c764-4b5d-9c0c-82e301ad6e45",
    "userId": "de4865aa-f81b-4fe0-9e62-6e5a8f674280",
    "invoice": {
      "id": "9544b2ae-c84a-4bc5-a7ed-599b20d0f25d",
      "status": "expired",
      "amount": 0.00123,
      "amountInDecimals": "1230",
      "reference": "INV-001990",
      "walletId": "5326036c-50c3-4d63-8150-69cb253542e3",
      "depositAddress": "0x37937c09c7454b1d4ff8fb7657cfd84418168036",
      "expiresAt": "2026-07-08T06:30:00.000000000Z",
      "bufferInSeconds": 300,
      "expiredAt": "2026-07-08T07:00:00.000000000Z"
    },
    "token": {
      "id": "53c22951-9740-47dc-82f0-db0179e617a7",
      "tokenCode": "usdc-polygon-pos",
      "symbol": "USDT",
      "decimals": 6
    }
  }
}

invoice.failed

Sent when a transfer arrives at an invoice deposit address but the amount does not match the expected invoice amount.

data fields

FieldTypeDescription
userIdstringEnd-user UUID
reasonstringFailure reason (currently wrong_amount)
invoiceobjectInvoice that was targeted
transactionobjectOn-chain transfer that failed to match
tokenobjectToken metadata
{
  "apiVersion": "2025-06-22",
  "id": "invoice.failed:9544b2ae-c84a-4bc5-a7ed-599b20d0f25d:0xabc123...",
  "type": "invoice.failed",
  "createdAt": "2026-07-08T06:25:00.000000000Z",
  "data": {
    "userId": "de4865aa-f81b-4fe0-9e62-6e5a8f674280",
    "reason": "wrong_amount",
    "invoice": {
      "id": "9544b2ae-c84a-4bc5-a7ed-599b20d0f25d",
      "status": "pending",
      "amount": 0.00123,
      "amountInDecimals": "1230",
      "reference": "INV-001990",
      "walletId": "5326036c-50c3-4d63-8150-69cb253542e3",
      "depositAddress": "0x37937c09c7454b1d4ff8fb7657cfd84418168036"
    },
    "transaction": {
      "txHash": "0xabc123...",
      "chainId": 137,
      "networkCode": "polygon-pos",
      "from": "0x0f6c4A746C303a2ad7E008302d61f615D97dfe3e",
      "to": "0x37937c09C7454B1d4Ff8fB7657cFd84418168036",
      "tokenAddress": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
      "value": "500",
      "valueDecimals": 6,
      "blockchainType": "evm"
    },
    "token": {
      "id": "53c22951-9740-47dc-82f0-db0179e617a7",
      "tokenCode": "usdc-polygon-pos",
      "symbol": "USDT",
      "decimals": 6
    }
  }
}

withdraw.created

Sent when a withdrawal request is accepted and enters the processing pipeline.

data fields

FieldTypeDescription
appIdstringYour app UUID
userIdstringEnd-user UUID
withdrawobjectWithdrawal details
tokenobjectToken metadata
networkobjectTarget network

withdraw object

FieldTypeDescription
idstringWithdrawal UUID
statusstringpending
amountnumberRequested amount
netAmountnumberAmount sent to recipient after fees
totalDeductednumberTotal deducted from balance
withdrawGasFeenumberGas fee component
amountInDecimalsstringAmount in smallest unit
recipientAddressstringDestination address
referencestring | nullYour reference
withdrawWalletIdstringSource wallet UUID
withdrawTransactionFeestringFee mode (e.g. exclusive)
txHashstring | nullOn-chain hash (null until broadcast)
createdAtstringUTC timestamp
updatedAtstringUTC timestamp

network object

FieldTypeDescription
codestringNetwork code
chainIdnumberChain ID
typestringBlockchain type
{
  "apiVersion": "2025-06-22",
  "id": "withdraw.created:wd_abc123",
  "type": "withdraw.created",
  "createdAt": "2026-07-08T08:00:00.000000000Z",
  "data": {
    "appId": "4f83980c-c764-4b5d-9c0c-82e301ad6e45",
    "userId": "de4865aa-f81b-4fe0-9e62-6e5a8f674280",
    "withdraw": {
      "id": "wd_abc123",
      "status": "pending",
      "amount": 50,
      "netAmount": 49.5,
      "totalDeducted": 50.5,
      "withdrawGasFee": 0.5,
      "amountInDecimals": "50000000",
      "recipientAddress": "0xdef...",
      "reference": "WD-001",
      "withdrawWalletId": "wal_1",
      "withdrawTransactionFee": "exclusive",
      "txHash": null,
      "createdAt": "2026-07-08T08:00:00.000000000Z",
      "updatedAt": "2026-07-08T08:00:00.000000000Z"
    },
    "token": {
      "id": "53c22951-9740-47dc-82f0-db0179e617a7",
      "tokenCode": "usdt-ethereum",
      "symbol": "USDT",
      "decimals": 6
    },
    "network": {
      "code": "ethereum",
      "chainId": 1,
      "type": "evm"
    }
  }
}

withdraw.success

Sent when a withdrawal completes successfully on-chain. Same data shape as withdraw.created, with status: "success" and txHash populated.

{
  "apiVersion": "2025-06-22",
  "id": "withdraw.success:wd_abc123",
  "type": "withdraw.success",
  "createdAt": "2026-07-08T08:05:00.000000000Z",
  "data": {
    "appId": "4f83980c-c764-4b5d-9c0c-82e301ad6e45",
    "userId": "de4865aa-f81b-4fe0-9e62-6e5a8f674280",
    "withdraw": {
      "id": "wd_abc123",
      "status": "success",
      "amount": 50,
      "netAmount": 49.5,
      "totalDeducted": 50.5,
      "withdrawGasFee": 0.5,
      "amountInDecimals": "50000000",
      "recipientAddress": "0xdef...",
      "reference": "WD-001",
      "withdrawWalletId": "wal_1",
      "withdrawTransactionFee": "exclusive",
      "txHash": "0x789...",
      "createdAt": "2026-07-08T08:00:00.000000000Z",
      "updatedAt": "2026-07-08T08:05:00.000000000Z"
    },
    "token": {
      "id": "53c22951-9740-47dc-82f0-db0179e617a7",
      "tokenCode": "usdt-ethereum",
      "symbol": "USDT",
      "decimals": 6
    },
    "network": {
      "code": "ethereum",
      "chainId": 1,
      "type": "evm"
    }
  }
}

withdraw.failed

Sent when a withdrawal fails. Same data shape as withdraw.created, with status: "failed".

{
  "apiVersion": "2025-06-22",
  "id": "withdraw.failed:wd_abc123",
  "type": "withdraw.failed",
  "createdAt": "2026-07-08T08:05:00.000000000Z",
  "data": {
    "appId": "4f83980c-c764-4b5d-9c0c-82e301ad6e45",
    "userId": "de4865aa-f81b-4fe0-9e62-6e5a8f674280",
    "withdraw": {
      "id": "wd_abc123",
      "status": "failed",
      "amount": 50,
      "netAmount": 49.5,
      "totalDeducted": 50.5,
      "withdrawGasFee": 0.5,
      "amountInDecimals": "50000000",
      "recipientAddress": "0xdef...",
      "reference": "WD-001",
      "withdrawWalletId": "wal_1",
      "withdrawTransactionFee": "exclusive",
      "txHash": null,
      "createdAt": "2026-07-08T08:00:00.000000000Z",
      "updatedAt": "2026-07-08T08:05:00.000000000Z"
    },
    "token": {
      "id": "53c22951-9740-47dc-82f0-db0179e617a7",
      "tokenCode": "usdt-ethereum",
      "symbol": "USDT",
      "decimals": 6
    },
    "network": {
      "code": "ethereum",
      "chainId": 1,
      "type": "evm"
    }
  }
}

webhook.test

Sent when you call POST /api/v1/webhooks/test. Use it to verify your endpoint and signature verification.

{
  "apiVersion": "2025-06-22",
  "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "type": "webhook.test",
  "createdAt": "2026-07-08T09:00:00.000000000Z",
  "data": {
    "appId": "4f83980c-c764-4b5d-9c0c-82e301ad6e45",
    "message": "This is a test webhook from Xellar Custodian API."
  }
}

Respond with HTTP 200 to acknowledge. Non-2xx responses are retried with exponential backoff (up to 5 attempts).

Verifying the Signature

The signature is computed over event_id + "\n" + timestamp + "\n" + raw_body. Fetch the public key from the public endpoint:

GET /v1/webhooks/public-key

{ "algorithm": "ed25519", "publicKey": "<base64>" }

Example (Node.js)

const crypto = require('crypto');
 
function verifyWebhook(publicKeyB64, headers, rawBody) {
  const message = `${headers['x-xellar-event-id']}\n${headers['x-xellar-timestamp']}\n${rawBody}`;
 
  const keyObject = crypto.createPublicKey({
    key: Buffer.from(publicKeyB64, 'base64'),
    format: 'der',
    type: 'spki',
  });
 
  return crypto.verify(
    null,
    Buffer.from(message),
    keyObject,
    Buffer.from(headers['x-xellar-signature'], 'base64'),
  );
}
 
app.post('/webhook', express.raw({ type: '*/*' }), (req, res) => {
  const ok = verifyWebhook(process.env.XELLAR_WEBHOOK_PUBKEY, req.headers, req.body.toString());
  if (!ok) return res.status(401).send('Invalid signature');
  res.status(200).send('OK');
});

Best Practices

  • Always verify the signature before trusting an event.
  • Return 200 quickly and process asynchronously.
  • Use X-Xellar-Event-Id (or the JSON id field) for idempotency to avoid double-processing retries.
  • Branch on the type field to handle each event explicitly.