Webhooks
Receive real-time HTTP notifications when payment events occur.
Near-instant
Seconds after confirmation
Auto-retry
3 attempts, exponential backoff
HMAC signed
SHA-256 signature header
Events
| Event | Description |
|---|---|
| payment.created | Payment was created |
| payment.confirmed | Deposit received and confirmed |
| payment.expired | Payment expired (15 min timeout) |
| payment.failed | Payment failed (under-payment, error) |
| payout.completed | Withdrawal processed successfully |
HTTP Headers
| Header | Value |
|---|---|
| Content-Type | application/json |
| X-PayCrypt-Event | Event name (e.g. payment.confirmed) |
| X-PayCrypt-Signature | HMAC SHA-256 signature — always present, verification required |
Payload Examples
{
"event": "payment.created",
"payment_id": "9515b51e-0279-4294-805d-91f7762914c3",
"order_id": "ord-12345",
"status": "pending",
"amount": 50,
"coin": "USDT",
"network": "ethereum",
"currency": "USD",
"deposit_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68",
"expires_at": "2026-02-20T10:10:46.000Z",
"created_at": "2026-02-20T09:55:46.541Z"
}{
"event": "payment.confirmed",
"payment_id": "9515b51e-0279-4294-805d-91f7762914c3",
"order_id": "ord-12345",
"status": "confirmed",
"amount": 50,
"amount_received": 50.02,
"amount_received_usd": 50.02,
"commission_rate_percent": 0.2,
"commission_amount_usd": 0.10,
"amount_net_usd": 49.92,
"coin": "USDT",
"network": "ethereum",
"currency": "USD",
"deposit_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68",
"tx_hash": "0x8a2f7b3c9d1e5f6a...",
"confirmations": 12,
"confirmed_at": "2026-02-20T10:02:15.844Z",
"customer_email": "customer@example.com",
"metadata": { "plan": "pro" }
}{
"event": "payment.expired",
"payment_id": "9515b51e-0279-4294-805d-91f7762914c3",
"order_id": "ord-12345",
"status": "expired",
"amount": 50,
"coin": "USDT",
"network": "ethereum",
"currency": "USD",
"deposit_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68",
"expires_at": "2026-02-20T10:10:46.000Z",
"created_at": "2026-02-20T09:55:46.541Z"
}Webhook Secret
A webhook signing secret is automatically generated when you save a webhook URL in the Dashboard. You can view and manage it in Dashboard → API & Webhooks → Webhook Signing Secret.
Reveal
Click Reveal and enter your account password to view the secret.
Copy
Copy the secret and add it to your server's environment variables as PAYCRYPT_WEBHOOK_SECRET.
Regenerate
Generate a new secret at any time. The old secret is immediately invalidated — update your server code.
Signature Verification
Every webhook includes an X-PayCrypt-Signature header. Signature verification is mandatory — always validate the HMAC SHA-256 signature before processing the payload. Reject any request with an invalid or missing signature.
const crypto = require('crypto');
function verifyWebhookSignature(rawBody, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
const sig = signature.replace('sha256=', '');
return crypto.timingSafeEqual(
Buffer.from(sig, 'hex'),
Buffer.from(expected, 'hex')
);
}
// Express handler
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-paycrypt-signature'];
if (!signature) return res.status(401).send('Missing signature');
const isValid = verifyWebhookSignature(req.body, signature, process.env.PAYCRYPT_WEBHOOK_SECRET);
if (!isValid) return res.status(401).send('Invalid signature');
const event = JSON.parse(req.body.toString());
console.log('Event:', event.event, event.payment_id);
res.status(200).send('OK');
});<?php
$rawBody = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_PAYCRYPT_SIGNATURE'] ?? '';
$secret = getenv('PAYCRYPT_WEBHOOK_SECRET');
if (empty($secret)) {
http_response_code(500);
die('Webhook secret not configured');
}
if (empty($signature)) {
http_response_code(401);
die('Missing signature');
}
$expected = hash_hmac('sha256', $rawBody, $secret);
$sig = str_replace('sha256=', '', $signature);
if (!hash_equals($expected, $sig)) {
http_response_code(401);
die('Invalid signature');
}
$event = json_decode($rawBody, true);
// Handle $event['event'], $event['payment_id'], etc.
http_response_code(200);
echo 'OK';Retry Policy
If your endpoint returns a non-2xx status code or times out, PayCrypt retries with exponential backoff:
| Attempt | Delay |
|---|---|
| 1st attempt | Immediate |
| 2nd attempt | ~30 seconds |
| 3rd attempt | ~2 minutes |