Set up webhookUrl to receive real-time notifications about transaction status changes.
Webhook Events
We send notifications for the following events:
| Event Type | Description |
|---|
transaction.pending | Payment being processed |
transaction.transferring | Cryptocurrency transfer in progress |
transaction.completed | Cryptocurrency sent successfully |
transaction.failed | Transaction failed |
All webhook notifications follow a consistent format with an event type, ID, timestamp, and data payload. See the examples below for each event type.
Webhook Event Examples
transaction.pending
{
"eventType": "transaction.pending",
"eventId": "01987ad3-c66e-7626-8bf3-65d5a58f7e59",
"timestamp": "2025-08-05T15:22:35Z",
"data": {
"paymentRequestId": "464709b4X3jp5869f69abd0703bf12ef",
"status": "pending",
"paymentDetails": {
"fiatAmount": "1.5",
"fiatCurrency": "EUR",
"cryptoAmount": "1.62",
"cryptoCurrency": "USDC",
"destinationAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f8E9A7",
"network": "polygon",
"paymentMethod": "card"
},
"customerDetails": {
"email": "[email protected]"
},
"fees": {
"EUR": {
"partnerFee": "0.03",
"totalFee": "0.11",
"networkFee": "0.009",
"transactionFee": "0.071"
},
"USD": {
"partnerFee": "0.03",
"totalFee": "0.11",
"networkFee": "0.01",
"transactionFee": "0.07"
}
},
"metadata": {
"someotherfield": 1234,
"yourfield": "loremipsum"
}
}
}
transaction.transferring
{
"eventType": "transaction.transferring",
"eventId": "01987ad3-ddd1-72af-b131-c9c68fd30da3",
"timestamp": "2025-08-05T15:22:41Z",
"data": {
"paymentRequestId": "464709b4X3jp5869f69abd0703bf12ef",
"status": "pending",
"paymentDetails": {
"fiatAmount": "1.5",
"fiatCurrency": "EUR",
"cryptoAmount": "1.62",
"cryptoCurrency": "USDC",
"destinationAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f8E9A7",
"network": "polygon",
"paymentMethod": "card"
},
"customerDetails": {
"email": "[email protected]"
},
"fees": {
"EUR": {
"partnerFee": "0.02",
"totalFee": "0.09",
"networkFee": "0.009",
"transactionFee": "0.061"
},
"USD": {
"partnerFee": "0.03",
"totalFee": "0.11",
"networkFee": "0.01",
"transactionFee": "0.07"
}
},
"metadata": {
"someotherfield": 1234,
"yourfield": "loremipsum"
}
}
}
transaction.completed
{
"eventType": "transaction.completed",
"eventId": "01987ad5-2a26-7398-ae88-9e88a7110405",
"timestamp": "2025-08-05T15:24:07Z",
"data": {
"paymentRequestId": "464709b4X3jp5869f69abd0703bf12ef",
"status": "completed",
"transactionDetails": {
"transactionHash": "0x1989a97e4d5ff48f204006e88ca21374835ab1e488baec8b4de1b164f7955cdb",
"blockExplorerUrl": "https://polygonscan.com/tx/0x1989a97e4d5ff48f204006e88ca21374835ab1e488baec8b4de1b164f7955cdb"
},
"paymentDetails": {
"fiatAmount": "1.5",
"fiatCurrency": "EUR",
"cryptoAmount": "1.62",
"cryptoCurrency": "USDC",
"destinationAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f8E9A7",
"network": "polygon",
"paymentMethod": "card"
},
"customerDetails": {
"email": "[email protected]"
},
"fees": {
"EUR": {
"partnerFee": "0.02",
"totalFee": "0.09",
"networkFee": "0.009",
"transactionFee": "0.061"
},
"USD": {
"partnerFee": "0.03",
"totalFee": "0.11",
"networkFee": "0.01",
"transactionFee": "0.07"
}
},
"metadata": {
"someotherfield": 1234,
"yourfield": "loremipsum"
}
}
}
transaction.failed
{
"eventType": "transaction.failed",
"eventId": "01987ad7-12df-7bb2-908c-9d5d48fa895d",
"timestamp": "2025-08-05T15:26:12Z",
"data": {
"paymentRequestId": "0b51711fX3jpc1d426a91d48dd43478d",
"status": "failed",
"paymentDetails": {
"fiatAmount": "1.5",
"fiatCurrency": "EUR",
"cryptoAmount": "1.62",
"cryptoCurrency": "USDC",
"destinationAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f8E9A7",
"network": "polygon",
"paymentMethod": "card"
},
"customerDetails": {
"email": "[email protected]"
},
"fees": {
"EUR": {
"partnerFee": "0.03",
"totalFee": "0.11",
"networkFee": "0.009",
"transactionFee": "0.071"
},
"USD": {
"partnerFee": "0.03",
"totalFee": "0.11",
"networkFee": "0.01",
"transactionFee": "0.07"
}
},
"metadata": {
"someotherfield": 1234,
"yourfield": "loremipsum"
}
}
}
Webhook Security
Signature Verification
All webhooks include a signature in the X-Webhook-Signature header. Verify this signature to ensure the webhook is authentic:
X-Webhook-Signature: sha256_1234567890abcdef...
The signature is computed using HMAC-SHA256 with your webhook secret (provided during onboarding).
Verification Examples
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const bodyString = JSON.stringify(payload);
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(bodyString)
.digest('hex');
return signature === `sha256_${expectedSignature}`;
}
app.post('/webhook', (req, res) => {
const signature = req.headers['x-webhook-signature'];
const isValid = verifyWebhookSignature(req.body, signature, WEBHOOK_SECRET);
if (!isValid) {
return res.status(401).send('Invalid signature');
}
// Process the webhook
handleWebhook(req.body);
res.status(200).send('OK');
});
Handling Webhooks
Best Practices
- Respond quickly - Return a 2xx status code immediately, then process asynchronously
- Implement idempotency - Use the
eventId to prevent duplicate processing
- Verify signatures - Always verify webhook signatures before processing
- Handle retries - Be prepared to receive the same event multiple times
- Log everything - Keep detailed logs for debugging and reconciliation
Example Implementation
const webhookHandlers = {
'transaction.pending': (data) => {
console.log('Payment processing:', data.paymentRequestId);
// Notify user that payment is being processed
},
'transaction.transferring': (data) => {
console.log('Transfer in progress:', data.paymentRequestId);
// Notify user that crypto transfer is happening
},
'transaction.completed': (data) => {
console.log('Transaction completed:', data.transactionDetails.transactionHash);
// Fulfill the order
// Send confirmation email
// Update inventory
},
'transaction.failed': (data) => {
console.log('Transaction failed:', data.paymentRequestId);
// Notify user of failure
// Suggest retry or alternative payment
}
};
async function handleWebhook(webhook) {
const { eventType, data } = webhook;
// Check if we've already processed this event
if (await isEventProcessed(webhook.eventId)) {
return;
}
// Process based on event type
const handler = webhookHandlers[eventType];
if (handler) {
await handler(data);
await markEventProcessed(webhook.eventId);
}
}
Webhook Retries
If your endpoint doesn’t respond with a 2xx status code, we’ll retry the webhook with exponential backoff:
- 1st retry: After 1 minute
- 2nd retry: After 2 minutes
- 3rd retry: After 4 minutes
- 4th retry: After 8 minutes
- 5th retry: After 16 minutes
- …
- 10th retry: After 512 minutes
After 10 failed attempts, we’ll stop retrying and mark the webhook as failed.
Always respond to webhooks quickly (within 5 seconds) with a 2xx status code. Process the webhook asynchronously to avoid timeouts.
Never expose your webhook secret in client-side code or public repositories. Keep it secure on your server.