Webhooks allow IteraPay to push real-time notifications to your server whenever a payment event occurs — such as an invoice being paid or a transaction being confirmed. Rather than polling the API, you register an endpoint on your server and IteraPay sends HTTP POST requests to it as events happen. This guide covers how to create your webhook, verify its authenticity, and handle incoming event payloads.
Always verify the webhook signature on every incoming request. Without signature verification, your endpoint is vulnerable to spoofed payloads from unauthorized sources.
Prerequisites
- A publicly accessible HTTPS endpoint on your server
- Your
merchantID and a valid API key or JWT bearer token
Register a webhook endpoint
Create or update your webhook
Register your endpoint URL and a secret that IteraPay will use to sign payloads. If a webhook already exists for your merchant account, this request will update it.curl --request POST \
--url 'https://api.iterapay.com/weebhook/{merchantID}' \
--header 'Authorization: Bearer YOUR_JWT_TOKEN' \
--header 'Content-Type: application/json' \
--data '{
"url": "https://yoursite.com/webhooks/iterapay",
"secret": "your-webhook-secret-min-32-chars-long"
}'
Expected response:{
"token": "whk_eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Store the returned token securely. You’ll use it alongside your secret to verify that incoming webhook payloads genuinely originate from IteraPay.
Verify your webhook configuration
Confirm that your webhook is registered correctly by retrieving the current configuration.curl --request GET \
--url 'https://api.iterapay.com/weebhook/{merchantID}' \
--header 'Authorization: Bearer YOUR_JWT_TOKEN'
Expected response:{
"url": "https://yoursite.com/webhooks/iterapay",
"createdAt": "2024-01-01T10:00:00Z",
"updatedAt": "2024-01-01T10:00:00Z"
}
Implement your webhook handler
Set up an HTTP endpoint on your server to receive POST requests from IteraPay. Your handler must:
- Read the raw request body (before any JSON parsing)
- Verify the signature (see the next section)
- Process the event
- Return a
200 status code promptly
const express = require('express');
const crypto = require('crypto');
const app = express();
// Use raw body parser to preserve the original payload for signature verification
app.use('/webhooks/iterapay', express.raw({ type: 'application/json' }));
app.post('/webhooks/iterapay', (req, res) => {
const signature = req.headers['x-iterapay-signature'];
const rawBody = req.body;
if (!verifySignature(rawBody, signature)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(rawBody);
switch (event.type) {
case 'invoice.paid':
// Handle paid invoice
break;
case 'transaction.confirmed':
// Handle confirmed transaction
break;
default:
console.log(`Unhandled event type: ${event.type}`);
}
res.status(200).send('OK');
});
function verifySignature(payload, signature) {
const hmac = crypto.createHmac('sha256', process.env.WEBHOOK_SECRET);
const computed = hmac.update(payload).digest('hex');
return crypto.timingSafeEqual(
Buffer.from(computed),
Buffer.from(signature)
);
}
import hmac
import hashlib
from flask import Flask, request, abort
app = Flask(__name__)
WEBHOOK_SECRET = 'your-webhook-secret-min-32-chars-long'
@app.route('/webhooks/iterapay', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-Iterapay-Signature')
raw_body = request.get_data()
if not verify_signature(raw_body, signature):
abort(401)
event = request.get_json(force=True)
event_type = event.get('type')
if event_type == 'invoice.paid':
# Handle paid invoice
pass
elif event_type == 'transaction.confirmed':
# Handle confirmed transaction
pass
return 'OK', 200
def verify_signature(payload, signature):
computed = hmac.new(
WEBHOOK_SECRET.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(computed, signature)
Verify webhook signatures
Every request from IteraPay includes a signature header that you must validate before processing the payload. This confirms the request is genuine and the payload has not been tampered with.
The signature is an HMAC-SHA256 hash of the raw request body, signed using your webhook secret.
Verification steps:
- Extract the
X-Iterapay-Signature header value
- Compute
HMAC-SHA256(rawBody, yourSecret)
- Compare the computed hash to the header value using a constant-time comparison function (e.g.,
timingSafeEqual in Node.js or hmac.compare_digest in Python)
- Reject the request if the values do not match
Use a constant-time comparison function to prevent timing attacks. Do not use === or == to compare the signature strings directly.
Example webhook payload
IteraPay sends a JSON payload to your endpoint for each event. The structure includes an event type and an associated data object.
{
"type": "invoice.paid",
"merchantID": "merchant_e5f6a7b8-c9d0-1234-efab-345678901234",
"timestamp": "2024-01-01T11:30:00Z",
"data": {
"invoiceID": "inv_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"amount": "100.500000",
"currency": "USDT",
"paidAt": "2024-01-01T11:30:00Z",
"txHash": "a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef12345678"
}
}
Common event types
| Event type | When it fires |
|---|
invoice.paid | A customer sends payment to an invoice address |
transaction.confirmed | An on-chain transaction reaches the required number of confirmations |
collection.completed | A fund collection sweep finishes |
withdrawal.completed | A withdrawal transaction is confirmed on-chain |
Remove a webhook
To stop receiving webhook notifications, delete your webhook configuration:
curl --request DELETE \
--url 'https://api.iterapay.com/weebhook/{merchantID}' \
--header 'Authorization: Bearer YOUR_JWT_TOKEN'
A successful deletion returns a 200 or 204 response with no body.
Next steps
Now that you’re receiving payment events in real time, you can automate your order fulfillment, update your database, or trigger downstream workflows on each payment confirmation.