· 4 min · TextMeFlow Team

Inkomende WhatsApp-berichten doorsturen naar Slack met webhooks

Klanten die via WhatsApp reageren op een boekingsbevestiging of factuur, willen we niet laten wachten tot iemand toevallig in de admin-omgeving kijkt. De meeste teams zitten de hele dag in Slack — dus daar moet dat bericht landen. TextMeFlow stuurt elk inkomend WhatsApp-bericht als webhook naar jouw server; met een klein tussenstukje code komt dat rechtstreeks in een Slack-kanaal terecht, inclusief afzendernaam en telefoonnummer.

Dit artikel bouwt de volledige flow: van Slack incoming webhook tot een geverifieerd, idempotent endpoint dat berichten doorstuurt.

Wat je nodig hebt

  • Een TextMeFlow-account met een gekoppeld WhatsApp-nummer en een geconfigureerde webhook_url in /admin/webhook
  • Een Slack-werkruimte waar je een incoming webhook mag aanmaken
  • Een kleine server (Node/Express in dit voorbeeld, maar hetzelfde werkt in PHP of Python)

Stap 1: Slack incoming webhook aanmaken

Maak in Slack een nieuwe app aan (of hergebruik een bestaande), activeer Incoming Webhooks en koppel een kanaal, bijvoorbeeld #whatsapp-inbox. Slack geeft je een URL zoals https://hooks.slack.com/services/T000/B000/XXXX. Bewaar die als omgevingsvariabele — nooit hardcoded in je repo.

Stap 2: endpoint bouwen dat de TextMeFlow-webhook verifieert

TextMeFlow post voor elk inkomend bericht een JSON-body naar jouw webhook_url, ondertekend met HMAC-SHA256 in de header X-TextMeFlow-Signature (vorm: sha256=<hex>). Verifieer die signature altijd voor je het bericht vertrouwt — anders kan iedereen die je endpoint-URL kent nepberichten insturen. Zie ook het artikel over webhook-signatures verifiëren voor de volledige uitleg van deze headers.

const express = require('express');
const crypto = require('crypto');

const app = express();
app.use(express.raw({ type: 'application/json' }));

const SECRET = process.env.TMF_WEBHOOK_SECRET;
const SLACK_URL = process.env.SLACK_WEBHOOK_URL;
const seen = new Set(); // vervang door een DB-tabel in productie

app.post('/textmeflow-inbound', async (req, res) => {
  const raw = req.body;
  const expected = 'sha256=' + crypto.createHmac('sha256', SECRET).update(raw).digest('hex');
  const received = req.headers['x-textmeflow-signature'] || '';

  if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received))) {
    return res.sendStatus(401);
  }

  const eventId = req.headers['x-textmeflow-event-id'];
  if (seen.has(eventId)) return res.sendStatus(200); // retry, al verwerkt
  seen.add(eventId);

  res.sendStatus(200); // eerst bevestigen, dan pas verwerken
  forwardToSlack(JSON.parse(raw));
});

app.listen(3000);

Merk op dat we eerst 200 terugsturen en pas daarna naar Slack posten. TextMeFlow verwacht een antwoord binnen 10 seconden; als je synchroon op een trage downstream-call (zoals Slack) wacht, loop je het risico op een onterechte retry.

Stap 3: bericht doorsturen naar Slack

De payload van TextMeFlow bevat type, from, from_name, to, file en message — exact dezelfde velden als TextMeBot, dus een bestaande integratie migreert 1-op-1. Bouw daar een leesbaar Slack-bericht van:

async function forwardToSlack(payload) {
  const { type, from, from_name, message } = payload;
  const label = type === 'text' ? '' : ` (${type})`;

  await fetch(SLACK_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      text: `📩 *${from_name || from}* (+${from})${label}: ${message}`,
    }),
  });
}

Voor image, document of location berichten staat de bestandsnaam of "<lat>,<lng>" in message — media-rehosting met een echte URL volgt in een latere release, dus vermeld voorlopig gewoon het type erbij zodat je collega's weten dat er een bijlage aankwam.

Retries en idempotency niet vergeten

Een mislukte aflevering (timeout, 5xx, 429) wordt tot 5 keer opnieuw geprobeerd met oplopende backoff (1m, 5m, 30m, 2u, 6u). Zonder de seen-check hierboven krijgt je Slack-kanaal bij elke retry een dubbel bericht. Gebruik in productie geen Set in het geheugen — die verdwijnt bij een herstart — maar een unieke constraint op X-TextMeFlow-Event-Id in je database, zodat een dubbele insert gewoon faalt en genegeerd wordt.

Verder uitbreiden

Deze basisflow is in een paar minuten uit te breiden: route berichten naar verschillende Slack-kanalen op basis van het to-nummer (handig met meerdere gekoppelde toestellen vanaf het Business-plan), of voeg een "Antwoorden"-knop toe die via de /v1/messages endpoint een reply terugstuurt naar de klant zonder dat iemand WhatsApp zelf hoeft te openen. De volledige veldreferentie en headerlijst staan in de webhook-documentatie.

Wil je dit zelf uittesten? Het gratis plan geeft je 50 berichten per maand, voor altijd, genoeg om deze hele flow op te zetten en te valideren voor je overstapt naar een betaald plan. Start gratis.

Zelf WhatsApp-berichten versturen via API?

Gratis voor altijd tot 50 berichten/maand. QR scannen en binnen 5 minuten verstuur je je eerste bericht.

Gratis voor altijd