· 4 min · TextMeFlow Team

WhatsApp-berichten doorsturen naar Discord met webhooks

Niet elk team zit in Slack of Teams — veel kleinere bedrijven, communities en dev-teams runnen hun interne communicatie via Discord. Als klanten via WhatsApp reageren op een bevestiging, factuur of vraag, wil je dat bericht niet pas zien als iemand toevallig inlogt in de admin-omgeving. Met een klein tussenstukje code laat je TextMeFlow elk inkomend WhatsApp-bericht rechtstreeks in een Discord-kanaal posten.

Dit artikel bouwt de volledige flow: van Discord webhook-URL tot een geverifieerd endpoint dat berichten doorstuurt, zonder dubbele meldingen bij retries.

Wat je nodig hebt

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

Stap 1: Discord-webhook aanmaken

Ga in Discord naar Server Settings → Integrations → Webhooks → New Webhook, kies het kanaal (bijvoorbeeld #whatsapp-inbox) en kopieer de webhook-URL. Die ziet er ongeveer zo uit: https://discord.com/api/webhooks/123456789/xxxxx. Bewaar die URL als omgevingsvariabele, nooit hardcoded in je repo — iedereen met die URL kan berichten in je kanaal posten.

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 raadt nepberichten insturen. De volledige uitleg van deze headers staat in het artikel over webhook-signatures verifiëren.

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 DISCORD_URL = process.env.DISCORD_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
  forwardToDiscord(JSON.parse(raw));
});

app.listen(3000);

We sturen eerst 200 terug en posten pas daarna naar Discord. TextMeFlow verwacht een antwoord binnen 10 seconden; wacht je synchroon op een trage downstream-call, dan loop je het risico op een onterechte retry.

Stap 3: bericht doorsturen naar Discord

De payload van TextMeFlow bevat type, from, from_name, to, file en message — dezelfde velden als TextMeBot, dus een bestaande integratie migreert vrijwel 1-op-1. Discord webhooks accepteren een simpele JSON-body met een content-veld:

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

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

Voor image, document of location-berichten staat de bestandsnaam of "<lat>,<lng>" in message. Vermeld voorlopig gewoon het type erbij, zodat je team weet 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 Discord-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 snel uit te breiden: route berichten naar verschillende Discord-kanalen op basis van het to-nummer (handig met meerdere gekoppelde toestellen vanaf het Business-plan), of gebruik Discord's embeds-veld voor een net iets overzichtelijker kaartje met afzender, tijdstip en berichttype apart. 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