Set up webhookUrl to receive real-time notifications about transaction status changes.

Webhook Events

We send notifications for the following events:
Event TypeDescription
transaction.pendingPayment being processed
transaction.transferringCryptocurrency transfer in progress
transaction.completedCryptocurrency sent successfully
transaction.failedTransaction failed

Webhook Format

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

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

  1. Respond quickly - Return a 2xx status code immediately, then process asynchronously
  2. Implement idempotency - Use the eventId to prevent duplicate processing
  3. Verify signatures - Always verify webhook signatures before processing
  4. Handle retries - Be prepared to receive the same event multiple times
  5. 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.