← All articles
How to Handle Crypto Payment Webhooks (With Code Examples)
Tutorial · CryptoGate Team · May 6, 2026 · 7 min read

How to Handle Crypto Payment Webhooks (With Code Examples)

Webhooks are how your server knows a payment landed. Here is exactly how to receive, verify, and act on them.

What Is a Webhook?

When a crypto payment confirms on-chain, the payment gateway sends an HTTP POST request to a URL you specify. This is called a webhook. Your server receives the request, checks that it is genuine, and updates the order accordingly.

Setting Up Your Webhook URL

In your CryptoGate dashboard, go to Settings → Webhooks and add your endpoint URL, for example:

https://yoursite.com/webhooks/cryptogate

Make sure this URL is publicly accessible (not localhost) and returns a 200 OK response when it receives a valid payload.

The Webhook Payload

CryptoGate sends a JSON body like this on a completed payment:

{
    "event": "payment.completed",
    "order_id": "order_123",
    "transaction_id": "TXN-abc123",
    "amount_requested": "49.99",
    "amount_received": "49.99",
    "currency": "USD",
    "coin": "USDT",
    "network": "tron",
    "txid": "abc123def456",
    "timestamp": 1746000000
  }

Verifying the Signature

Never trust a webhook without verifying it. CryptoGate includes an X-CryptoGate-Signature header with every request. It is an HMAC-SHA256 of the raw request body, signed with your webhook secret.

Verification in Node.js:

const crypto = require('crypto');

  function verifyWebhook(rawBody, signature, secret) {
    const expected = crypto
      .createHmac('sha256', secret)
      .update(rawBody)
      .digest('hex');
    return crypto.timingSafeEqual(
      Buffer.from(expected),
      Buffer.from(signature)
    );
  }

Verification in PHP:

function verifyWebhook($rawBody, $signature, $secret) {
    $expected = hash_hmac('sha256', $rawBody, $secret);
    return hash_equals($expected, $signature);
  }

Handling Events

CryptoGate sends webhooks for the following events:

For most stores, you only need to handle payment.completed to mark the order as paid and trigger fulfillment.

Idempotency — Handle Duplicates

Webhooks can occasionally be delivered more than once (network retries). Before fulfilling an order, check whether it has already been marked as paid:

const order = await db.orders.findOne({ id: payload.order_id });
  if (order.status === 'paid') {
    return res.status(200).send('already processed');
  }
  await db.orders.update({ id: payload.order_id }, { status: 'paid' });
  await fulfillOrder(payload.order_id);

Testing Webhooks Locally

Use a tunneling tool like ngrok to expose your local server during development:

ngrok http 3000

Copy the HTTPS URL ngrok provides and set it as your webhook URL in the CryptoGate dashboard. You can then use sandbox mode to trigger test events and inspect the full request payload.

Summary

Webhooks are straightforward: receive the POST, verify the HMAC signature, check idempotency, update the order, fulfill. The whole handler is under 30 lines of code regardless of your stack.

Ready to accept crypto payments?

Set up in minutes. No KYC required. Non-custodial — funds go directly to your wallet.

Get started free →