Inkomende WhatsApp-berichten doorsturen naar Microsoft Teams met webhooks
Veel bedrijven die op Microsoft 365 draaien, zitten de hele dag in Teams en niet in een los dashboard. Een klant die via WhatsApp reageert op een factuur of afspraakherinnering, moet dus in Teams landen — niet pas opgemerkt worden wanneer iemand toevallig inlogt op de TextMeFlow-admin. Dit artikel bouwt die flow: van een Teams-kanaal met een webhook-ingang tot een geverifieerd endpoint dat elk inkomend WhatsApp-bericht doorstuurt als kaart in Teams.
Belangrijk: gebruik de Workflows-app, niet de oude "Incoming Webhook"-connector
Microsoft heeft de klassieke Office 365-connectors (de oude "Incoming Webhook" in kanaalinstellingen) uitgefaseerd. De huidige weg loopt via de Workflows-app in Teams, gebouwd op Power Automate. Die geeft je dezelfde functionaliteit — een unieke URL die een POST-request omzet in een kaart in een kanaal — maar dan ondersteund en zonder deprecation-waarschuwing.
Stap 1: webhook-URL aanmaken in Teams
- Open het kanaal waar de berichten moeten landen, bijvoorbeeld
WhatsApp-inbox, en kies Workflows bij de kanaalopties. - Zoek de template "Post to a channel when a webhook request is received" en volg de wizard.
- Teams genereert een unieke URL. Bewaar die als omgevingsvariabele (
TEAMS_WEBHOOK_URL) — nooit hardcoded in je repository, want wie die URL kent kan berichten in je kanaal posten.
Stap 2: endpoint bouwen dat de TextMeFlow-webhook verifieert
TextMeFlow stuurt voor elk inkomend WhatsApp-bericht een JSON-body naar de webhook_url die je instelt in /admin/webhook, ondertekend met HMAC-SHA256 in de header X-TextMeFlow-Signature (vorm sha256=<hex>). Verifieer die signature altijd voordat je het bericht vertrouwt. 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 TEAMS_URL = process.env.TEAMS_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
forwardToTeams(JSON.parse(raw));
});
app.listen(3000);
Stuur eerst 200 terug en verwerk pas daarna richting Teams. TextMeFlow verwacht binnen 10 seconden een antwoord; wacht je synchroon op een trage downstream-call, dan loop je het risico op een onterechte retry.
Stap 3: bericht als kaart posten in Teams
De payload van TextMeFlow bevat type, from, from_name, to en message — dezelfde velden als bij TextMeBot, dus een bestaande integratie migreert 1-op-1. De webhook-trigger van de Workflows-app verwacht een JSON-body; het eenvoudigst is een platte tekstkaart:
async function forwardToTeams(payload) {
const { type, from, from_name, message } = payload;
const label = type === 'text' ? '' : ` (${type})`;
await fetch(TEAMS_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 — vermeld dus gewoon het type erbij zodat 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 Teams-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
Route berichten op basis van het to-nummer naar verschillende kanalen — handig zodra je met meerdere gekoppelde toestellen werkt vanaf het Business-plan. 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 flow helemaal 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