Webhooks
Webhooks allow you to receive real-time notifications when events occur in your Rebel Pay account, such as when a payment confirms.
Event Types
charge.created— A new charge was createdcharge.pending— Payment detected in mempool, awaiting confirmationscharge.confirmed— Payment confirmed on the blockchain (at your configured minimum: 3, 5, or 10 confirmations)charge.expired— Charge expired without payment (1 hour default)charge.underpaid— Payment received but amount is less than requiredpayout.sent— XMR payout swept to merchant's payout walletswap.auto_created— Auto-convert swap initiated on charge confirmation (see Auto-Convert)
Webhook Payload
All webhooks are sent as HTTP POST requests with JSON body:
POST https://yoursite.com/webhooks/rebelpay
Content-Type: application/json
X-REBELPAY-SIGNATURE: <hmac-sha256-signature>
{
"event": "charge.confirmed",
"data": {
"id": "ch_abc123def456",
"amount": 25.00,
"currency": "USD",
"amount_xmr": 0.071428,
"subaddress": "84Hv16y6x7BTie3ib5Sx...",
"status": "confirmed",
"tx_hash": "7d2e4f8a1b3c5d6e...",
"confirmations": 10, // actual count at confirmation time
"metadata": {
"order_id": "12345",
"buyer_fields": "email,name",
"buyer_info": {
"email": "customer@example.com",
"name": "Jane Smith"
}
},
"created_at": "2026-02-15T10:00:00.000Z",
"confirmed_at": "2026-02-15T10:20:00.000Z"
},
"timestamp": "2026-02-15T10:20:00.000Z"
}
Signature Verification
Every webhook includes an X-REBELPAY-SIGNATURE
header containing an HMAC-SHA256 signature of the request body.
Always verify signatures to ensure webhooks are genuine:
Node.js
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
// Use timing-safe comparison
return crypto.timingSafeEqual(
Buffer.from(signature, 'hex'),
Buffer.from(expected, 'hex')
);
}
// Express handler
app.post('/webhooks/rebelpay', express.raw({type: 'application/json'}), (req, res) => {
const signature = req.headers['x-rebelpay-signature'];
const payload = req.body.toString();
if (!verifyWebhookSignature(payload, signature, WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(payload);
switch (event.event) {
case 'charge.confirmed':
// Fulfill the order
fulfillOrder(event.data.metadata.order_id);
break;
case 'charge.expired':
// Handle expiration
cancelOrder(event.data.metadata.order_id);
break;
}
res.status(200).send('OK');
});
Python
import hmac
import hashlib
def verify_signature(payload: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)
# Flask handler
@app.route('/webhooks/rebelpay', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-REBELPAY-SIGNATURE')
payload = request.get_data()
if not verify_signature(payload, signature, WEBHOOK_SECRET):
return 'Invalid signature', 401
event = request.get_json()
if event['event'] == 'charge.confirmed':
fulfill_order(event['data']['metadata']['order_id'])
return 'OK', 200
PHP
<?php
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_REBELPAY_SIGNATURE'];
$secret = 'your_webhook_secret';
$expected = hash_hmac('sha256', $payload, $secret);
if (!hash_equals($expected, $signature)) {
http_response_code(401);
exit('Invalid signature');
}
$event = json_decode($payload, true);
if ($event['event'] === 'charge.confirmed') {
fulfillOrder($event['data']['metadata']['order_id']);
}
echo 'OK';
Webhook Secret
When you create a webhook, you receive a secret key. Store this securely - it's only shown once. If you lose it, delete and recreate the webhook.
Retry Policy
If your endpoint returns a non-2xx status code or the connection fails, we'll retry with exponential backoff:
- 1st retry: 5 seconds
- 2nd retry: 10 seconds
After 3 total attempts (initial + 2 retries), the delivery is marked as failed. All delivery attempts are logged in the webhook delivery log on your dashboard.
Security Best Practices
- Always verify signatures: Never process a webhook without validating the HMAC signature. Attackers can forge webhook payloads to trigger false fulfillments.
- Use timing-safe comparison: Use
crypto.timingSafeEqual(Node.js),hmac.compare_digest(Python), orhash_equals(PHP) to prevent timing attacks. - Validate charge status independently: After receiving a webhook, query
GET /api/charges/:idto confirm the status server-side before fulfilling orders. - Handle duplicates: Use the charge ID as an idempotency key. You may receive the same event more than once.
- Respond quickly: Return 200 within 10 seconds. Queue long-running tasks.
- Use HTTPS: Webhook URLs must use HTTPS in production.
- Don't trust metadata blindly: Metadata is merchant-supplied. Validate it against your own records.
- Buyer fields are buyer-supplied: Data in
metadata.buyer_infocomes from the customer. Treat it as untrusted input — validate and sanitize before use.
Testing Webhooks
Use services like webhook.site or ngrok to test webhooks during development.