Skip to content

Testing Webhooks ​

Test via Client Portal ​

Go to Client Portal → Dashboard → Webhooks and click "Send Test Webhook".

TokenCashFlow sends a synthetic payment.completed-shaped payload to your configured URL and shows you:

  • Whether delivery succeeded (HTTP status code from your server)
  • The payload that was sent

The test delivery is signed with your configured secret. Use this to verify your signature verification code end-to-end.

Response:

json
{
  "success": true,
  "data": {
    "delivered": true,
    "status_code": 200,
    "response_body": "",
    "duration_ms": 143,
    "payload_sent": {
      "event": "payment.completed",
      "timestamp": "2026-05-16T12:00:00.123456+00:00",
      "data": {
        "payment_id": "test-00000000-0000-0000-0000-000000000000",
        "status": "COMPLETED",
        "amount_usd": "100.00",
        "received_crypto": "0.050000000000000000",
        "crypto_symbol": "ETH",
        "chain": "ethereum",
        "locked_rate_usd": "2000.00",
        "fees": { "service_fee_usd": "0.50", "conversion_fee_usd": "1.00" },
        "net_amount_usd": "98.50",
        "completed_at": "2026-05-16T12:00:00.123456+00:00"
      }
    }
  }
}

The test payload's data.status is uppercase "COMPLETED" — this differs from real events, which use lowercase statuses. Treat status values case-insensitively or normalise them in your handler (e.g. status.lower()).

Local development with a tunnel ​

During development your webhook handler runs on localhost, which is not reachable by TokenCashFlow's servers. Use a tunnelling tool to expose a local port publicly.

Using ngrok ​

bash
# Install ngrok: https://ngrok.com/download
ngrok http 5000
# Output: https://a1b2c3d4.ngrok.io → http://localhost:5000

# Configure that URL in the Client Portal:
# https://a1b2c3d4.ngrok.io/webhook

Using Cloudflare Tunnel (free, no account needed for quick tests) ​

bash
# macOS/Linux
brew install cloudflared   # or download from https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/

cloudflared tunnel --url http://localhost:5000
# Output: https://random-name.trycloudflare.com

Minimal test server ​

Start a local server to log incoming webhooks during development:

python
# test_webhook_server.py
import hmac, hashlib, json
from flask import Flask, request

app = Flask(__name__)
SECRET = "your_webhook_secret_here"

@app.route("/webhook", methods=["POST"])
def webhook():
    body = request.get_data()
    sig = request.headers.get("X-TCF-Signature", "")
    expected = "sha256=" + hmac.new(SECRET.encode(), body, hashlib.sha256).hexdigest()

    if not hmac.compare_digest(expected, sig):
        print("⚠️  Signature mismatch!")
        return "Bad signature", 400

    payload = json.loads(body)
    print(f"✅ [{payload['event']}] payment_id={payload['data'].get('payment_id')}")
    print(json.dumps(payload, indent=2))
    return "", 200

if __name__ == "__main__":
    app.run(port=5000)

Run with:

bash
pip install flask
python test_webhook_server.py

Verifying signature in test deliveries ​

Both the portal test button and the API POST /v1/webhooks/test sign the payload with your configured secret. Your signature verification code should handle test deliveries identically to real ones — there is no separate test mode for signatures.

TokenCashFlow Documentation