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:
payment.completed— payment confirmed on-chain, safe to fulfillpayment.partial— customer sent less than the required amountpayment.expired— session timed out before payment arrivedpayment.overpaid— customer sent more than required
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.