Documentation

Inbound webhooks

Antwoorden van je gekoppelde WhatsApp-nummer realtime ontvangen. Drop-in compatibel met TextMeBot — bestaande integraties die type / from / from_name / to / file / message verwerken werken zonder aanpassingen.

1. Endpoint configureren

Open /admin/webhook in het customer portal:

  1. Plak je publieke HTTPS URL.
  2. Klik Opslaan — er wordt automatisch een 64-char HMAC-secret gegenereerd. Kopieer hem direct, hij wordt nadien gemaskeerd.
  3. Klik Test fire om een sample-payload naar je endpoint te sturen.

2. Payload (TextMeBot-compatibel)

Bij elk inkomend bericht doet TextMeFlow een POST met content-type application/json en deze body:

{
  "type": "text",
  "from": "32472932208",
  "from_name": "Jan Janssens",
  "to": "32499123456",
  "file": "null",
  "message": "Bedankt voor de bevestiging!"
}
VeldTypeInhoud
typestringtext, image, audio, video, document of location
fromstringAfzender, alleen cijfers (geen +)
from_namestringWhatsApp pushName, mogelijk leeg
tostringJouw gekoppelde nummer, alleen cijfers
filestringVoor text-berichten letterlijk de string "null" (matcht TextMeBot). Media-URL volgt in een latere release.
messagestringTekst of bijschrift; voor document de filename, voor location de string "lat,lng"

3. Headers

HeaderInhoud
Content-Typeapplication/json
User-AgentTextMeFlow-Webhook/1.0
X-TextMeFlow-Event-IdUUID per bericht — gebruik dit als idempotency-key, retries hergebruiken hetzelfde id.
X-TextMeFlow-Signaturesha256=<hex> — HMAC-SHA256 van de raw body met je webhook-secret.
X-TextMeFlow-DeliveryUUID per delivery-poging, handig voor log-grep.

4. Signature verifiëren

Bereken HMAC-SHA256 over de raw request body (niet over een geparseerd object — JSON-encoder verschillen breken anders je hash) en vergelijk constant-time met de sha256=… waarde uit de header.

PHP

// $secret = 'tmf_…' uit /admin/webhook
$raw = file_get_contents('php://input');
$expected = 'sha256=' . hash_hmac('sha256', $raw, $secret);
$received = $_SERVER['HTTP_X_TEXTMEFLOW_SIGNATURE'] ?? '';

if (! hash_equals($expected, $received)) {
    http_response_code(401);
    exit;
}
$body = json_decode($raw, true);

Node.js (Express)

import crypto from 'node:crypto';
import express from 'express';

const app = express();
// Capture raw body for HMAC; can't run after express.json() ate it.
app.use(express.raw({ type: 'application/json' }));

app.post('/textmeflow-inbound', (req, res) => {
  const raw = req.body; // Buffer
  const expected = 'sha256=' + crypto
    .createHmac('sha256', process.env.TMF_WEBHOOK_SECRET)
    .update(raw)
    .digest('hex');

  const received = req.get('X-TextMeFlow-Signature') ?? '';
  if (
    expected.length !== received.length ||
    !crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received))
  ) {
    return res.sendStatus(401);
  }

  const body = JSON.parse(raw.toString('utf8'));
  // …handle body…
  res.sendStatus(200);
});

Python (Flask)

import hmac, hashlib, os
from flask import Flask, request, abort

app = Flask(__name__)
SECRET = os.environ['TMF_WEBHOOK_SECRET'].encode()

@app.post('/textmeflow-inbound')
def inbound():
    raw = request.get_data()
    expected = 'sha256=' + hmac.new(SECRET, raw, hashlib.sha256).hexdigest()
    received = request.headers.get('X-TextMeFlow-Signature', '')
    if not hmac.compare_digest(expected, received):
        abort(401)
    body = request.get_json()
    # …handle body…
    return '', 200

5. Retry-policy

Een delivery is gelukt bij elke 2xx response binnen 10s. Andere uitkomsten:

ResponseGedrag
2xx✅ delivered, geen retry.
408 / 429 / 5xx🔁 retry — max 5 keer met backoff 1m, 5m, 30m, 2h, 6h.
andere 4xx❌ failed, geen retry. Een 401 betekent meestal dat je secret niet meer matcht — controleer en fix bij jouw kant.
timeout / netwerk🔁 retry zoals 5xx.

Na 6 totale pogingen (eerste + 5 retries) wordt de delivery als failed gemarkeerd. Status van elke poging zie je in /admin/webhook.

6. Migreren vanaf TextMeBot

Geen code-wijzigingen nodig — payload-shape is identiek. Eén actie:

  1. Plak je bestaande TextMeBot-endpoint URL in /admin/webhook.
  2. Voeg een signature-check toe (TextMeBot heeft die niet — verplichte upgrade voor productie). Zie de snippets hierboven.

Klaar om te ontvangen?

Configureer je endpoint in /admin/webhook en stuur een test-payload met één klik.