api

Webhooks

QuantaPay sends HTTP POST requests to your configured webhook URL when payment events occur. This allows your server to react to payments in real time.

Updated: 4/8/2026

QuantaPay sends HTTP POST requests to your configured webhook URL when payment events occur. This allows your server to react to payments in real time.

Setup

Configure your webhook in Settings → Payments → Webhook:

  • Webhook URL: Your HTTPS endpoint (e.g., https://yoursite.com/webhook/quantapay)
  • Webhook Secret Key: Auto-generated key for payload verification

Webhook Payload

When a payment is completed, QuantaPay sends a POST request with Content-Type: application/json:

{
  "key": "your-webhook-secret-key",
  "transaction": {
    "id": "197",
    "biz_id": "QP-20260408-560BF",
    "title": "",
    "description": "",
    "from": "0xCustomerWalletAddress",
    "to": "0xYourWalletAddress",
    "hash": "0xBlockchainTransactionHash",
    "amount": "0.025",
    "amount_fiat": "49.99000000",
    "cryptocurrency": "eth",
    "currency": "usd",
    "external_reference": "your-order-id-123",
    "creation_time": "2026-03-08 10:00:00",
    "status": "C",
    "webhook": "1",
    "vat": "0",
    "billing": "",
    "checkout_id": null,
    "type": "2"
  }
}

Transaction Fields

FieldTypeDescription
idstringQuantaPay internal transaction ID.
biz_idstringQuantaPay order reference (e.g., QP-20260408-560BF).
titlestringTransaction title.
fromstringCustomer's wallet address (sender).
tostringYour wallet address (receiver).
hashstringBlockchain transaction hash.
amountstringAmount in cryptocurrency.
amount_fiatstringAmount in fiat currency (8 decimal places, e.g., "49.99000000").
cryptocurrencystringCryptocurrency code (e.g., btc, eth, usdt).
currencystringFiat currency code (e.g., usd, eur, hkd).
external_referencestringYour order ID — passed when creating the Checkout Session. Use this to match to your order.
creation_timestringTransaction creation timestamp (UTC).
statusstringTransaction status: C (completed).
checkout_idstring|nullAssociated Checkout Session ID, or null.
typestringTransaction type: 1 = direct payment, 2 = Checkout Session.

Matching Orders with external_reference

This is the recommended way to link a QuantaPay payment to your own order.

When creating a Checkout Session, pass your order ID in the external_reference field:

{
  "order_id": "your-order-id-123",
  "external_reference": "your-order-id-123",
  "price": 49.99,
  "currency": "usd"
}

QuantaPay stores this value and returns it unchanged in the webhook payload under transaction.external_reference. Use it to find and fulfill the correct order.

Signature Verification

The key field in the payload contains your Webhook Secret Key. If no Webhook Secret Key is configured, the system falls back to using your account's encryption key instead. Always configure a dedicated Webhook Secret Key.

PHP Example

$payload = json_decode(file_get_contents('php://input'), true);

$webhook_secret = 'your-webhook-secret-key'; // From Settings

// 1. Verify webhook authenticity
if ($payload['key'] !== $webhook_secret) {
    http_response_code(401);
    die('Invalid webhook signature');
}

$transaction = $payload['transaction'];

// 2. Check payment is complete
if ($transaction['status'] !== 'C') {
    http_response_code(200);
    die('Not completed');
}

// 3. Match your order using external_reference
$your_order_id = $transaction['external_reference'];
$order = find_order($your_order_id);
if (!$order) {
    http_response_code(200);
    die('Order not found');
}

// 4. Verify amount using float comparison (amount_fiat has 8 decimal places)
$paid_amount = floatval($transaction['amount_fiat']);
$expected_amount = floatval($order->total);
if (abs($paid_amount - $expected_amount) > 0.01 || $transaction['currency'] !== $order->currency) {
    http_response_code(200);
    die('Amount mismatch');
}

// 5. Idempotency check — use biz_id as dedup key
if (order_already_fulfilled($transaction['biz_id'])) {
    http_response_code(200);
    die('Already processed');
}

// 6. Mark order as paid
fulfill_order($your_order_id);

http_response_code(200);
echo 'OK';

Node.js Example

const express = require('express');
const app = express();
app.use(express.json());

const WEBHOOK_SECRET = 'your-webhook-secret-key';

app.post('/webhook/quantapay', (req, res) => {
  const { key, transaction } = req.body;

  // 1. Verify webhook authenticity
  if (key !== WEBHOOK_SECRET) {
    return res.status(401).send('Invalid signature');
  }

  // 2. Only process completed payments
  if (transaction.status !== 'C') {
    return res.status(200).send('OK');
  }

  // 3. Match your order using external_reference
  const yourOrderId = transaction.external_reference;
  const bizId = transaction.biz_id; // use as dedup key

  console.log(`Payment completed: ${transaction.amount_fiat} ${transaction.currency}`);
  console.log(`Your order: ${yourOrderId}, QuantaPay ref: ${bizId}`);
  console.log(`TX Hash: ${transaction.hash}`);

  // Fulfill order logic here (use biz_id for idempotency)

  res.status(200).send('OK');
});

app.listen(3000);

cURL Test

curl -X POST https://yoursite.com/webhook/quantapay \
  -H "Content-Type: application/json" \
  -d '{
    "key": "your-webhook-secret-key",
    "transaction": {
      "id": "197",
      "biz_id": "QP-20260408-560BF",
      "status": "C",
      "amount": "0.025",
      "amount_fiat": "49.99000000",
      "cryptocurrency": "eth",
      "currency": "usd",
      "external_reference": "your-order-id-123",
      "hash": "0xabc123..."
    }
  }'

Event Types

StatusEventDescription
CPayment CompletedThe payment has been confirmed on the blockchain with sufficient confirmations.

Note: Webhooks are only sent for completed transactions (status C). Expired and pending transactions do not trigger webhooks.

Best Practices

1. Respond Quickly

Return an HTTP 200 response as soon as you receive the webhook. Do heavy processing asynchronously.

2. Idempotent Processing

Your webhook handler should be idempotent — processing the same webhook twice should not cause duplicate fulfillment. Use biz_id as the dedup key.

if (order_already_fulfilled($transaction['biz_id'])) {
    http_response_code(200);
    die('Already processed');
}

3. Verify the Key

Always verify the key field matches your Webhook Secret Key before processing.

4. Use HTTPS

Always use an HTTPS endpoint for your webhook URL.

5. Log Everything

Log all incoming webhooks for debugging and audit purposes.

error_log('QuantaPay Webhook: ' . json_encode($payload));

6. Handle Failures Gracefully

QuantaPay does not automatically retry failed webhook deliveries. Use the get-checkout-session or get-transactions API as a fallback to verify payment status if your server was temporarily unavailable.