Skip to content

Webhooks Overview

Webhooks allow TokenCashFlow to notify your server in real time when payment events occur — no polling required.

Why use webhooks

ApproachLatencyLoad
Polling GET /v1/payments/{id}Depends on your intervalN API calls per payment
WebhooksNear real-time0 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 PortalDashboard → 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 "", 200

Node.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() (not express.json()). HMAC signature validation must operate on the raw bytes of the request body — not a re-serialised JSON object.

Full reference

TokenCashFlow Documentation