Webhooks Overview
Webhooks allow TokenCashFlow to notify your server in real time when payment events occur — no polling required.
Why use webhooks
| Approach | Latency | Load |
|---|---|---|
Polling GET /v1/payments/{id} | Depends on your interval | N API calls per payment |
| Webhooks | Near real-time | 0 polling calls; one POST per event |
With webhooks you get instant notification the moment a payment is confirmed, allowing you to fulfil orders immediately.
Setup in 3 steps
Step 1 — Navigate to Client Portal → Dashboard → Webhooks
Step 2 — Enter your HTTPS endpoint URL and a signing secret (or generate one). Select which events to subscribe to (or leave as * for all).
Step 3 — Deploy the handler shown below and verify the signature on every request.
Minimal handler example
Python (Flask)
python
import hmac, hashlib
from flask import Flask, request, abort
app = Flask(__name__)
WEBHOOK_SECRET = "your_webhook_secret_here"
@app.route("/tcf-webhook", methods=["POST"])
def handle_webhook():
# 1. Verify signature FIRST — before reading body
sig_header = request.headers.get("X-TCF-Signature", "")
body = request.get_data()
expected = hmac.new(WEBHOOK_SECRET.encode(), body, hashlib.sha256).hexdigest()
received = sig_header.removeprefix("sha256=")
if not hmac.compare_digest(expected, received):
abort(400, "Invalid signature")
# 2. Process the event
event = request.json
if event["event"] == "payment.completed":
payment_id = event["data"]["payment_id"]
net_amount = event["data"]["net_amount_usd"]
# fulfil_order(payment_id, net_amount)
return "", 200Node.js (Express)
javascript
const crypto = require('crypto');
const express = require('express');
const app = express();
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;
app.post('/tcf-webhook', express.raw({ type: 'application/json' }), (req, res) => {
// 1. Verify signature FIRST
const sigHeader = req.headers['x-tcf-signature'] || '';
const expected = crypto.createHmac('sha256', WEBHOOK_SECRET)
.update(req.body).digest('hex');
const received = sigHeader.replace('sha256=', '');
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received))) {
return res.status(400).send('Invalid signature');
}
// 2. Process
const event = JSON.parse(req.body);
if (event.event === 'payment.completed') {
const { payment_id, net_amount_usd } = event.data;
// fulfilOrder(payment_id, net_amount_usd);
}
res.sendStatus(200);
});Important: Use
express.raw()(notexpress.json()). HMAC signature validation must operate on the raw bytes of the request body — not a re-serialised JSON object.
Full reference
- Webhook Events — all event types with payload examples
- Signature Verification — full verification guide with PHP example
- Retry Policy — delivery schedule and idempotency guidance
- Testing Webhooks — local development with tunnel tools

