Webhooks verwerken en handtekening verifiëren

Wesender stuurt realtime-events naar jouw endpoint zodra een e-mail bezorgd, geopend of gebounced wordt. Met webhooks kun je je database automatisch bijwerken zonder polling.

Beschikbare events

Event Betekenis
email.delivered E-mail succesvol bezorgd bij de ontvanger
email.bounced E-mail permanent geweigerd (hard bounce)
email.soft_bounced E-mail tijdelijk geweigerd (probeer opnieuw)
email.opened Ontvanger heeft de e-mail geopend
email.clicked Ontvanger heeft een link aangeklikt
email.complained Ontvanger heeft de e-mail als spam gemarkeerd

Stap 1: Webhook aanmaken in het dashboard

  1. Ga naar app.wesender.nl → Webhooks → Nieuw
  2. Vul je publiek bereikbare endpoint-URL in (bijv. https://mijnapp.nl/api/webhooks/email)
  3. Selecteer de events die je wilt ontvangen
  4. Kopieer de geheime sleutel — je hebt die nodig voor handtekeningverificatie

Stap 2: Handtekening verifiëren

Verifieer altijd de handtekening voordat je een event verwerkt. Dit voorkomt nep-verzoeken.

Node.js / Express

import express from "express"
import crypto from "crypto"

const app    = express()
const SECRET = process.env.WESENDER_WEBHOOK_SECRET!

// Raw body is nodig voor HMAC-verificatie
app.use("/api/webhooks/email", express.raw({ type: "application/json" }))

app.post("/api/webhooks/email", (req, res) => {
  const signature = req.headers["x-wesender-signature"] as string
  const body      = req.body as Buffer

  const verwacht = crypto
    .createHmac("sha256", SECRET)
    .update(body)
    .digest("hex")

  if (signature !== verwacht) {
    return res.status(401).json({ error: "Ongeldige handtekening" })
  }

  const event = JSON.parse(body.toString())
  verwerkEvent(event)

  // Altijd snel 200 teruggeven — Wesender probeert anders opnieuw
  res.json({ ok: true })
})

Next.js App Router

// app/api/webhooks/email/route.ts
import { NextRequest, NextResponse } from "next/server"
import crypto from "crypto"

const SECRET = process.env.WESENDER_WEBHOOK_SECRET!

export async function POST(req: NextRequest) {
  const body      = await req.arrayBuffer()
  const bodyBytes = Buffer.from(body)
  const signature = req.headers.get("x-wesender-signature") ?? ""

  const verwacht = crypto
    .createHmac("sha256", SECRET)
    .update(bodyBytes)
    .digest("hex")

  if (signature !== verwacht) {
    return NextResponse.json({ error: "Ongeldige handtekening" }, { status: 401 })
  }

  const event = JSON.parse(bodyBytes.toString())
  await verwerkEvent(event)

  return NextResponse.json({ ok: true })
}

Stap 3: Events verwerken

async function verwerkEvent(event: { type: string; data: Record<string, unknown> }) {
  switch (event.type) {
    case "email.delivered":
      // Markeer e-mail als bezorgd in je database
      await db.emails.update({ id: event.data.id, status: "delivered" })
      break

    case "email.bounced":
      // Voeg toe aan suppressielijst, stuur geen mails meer
      await db.suppressions.create({ email: event.data.to, reason: "bounce" })
      break

    case "email.complained":
      // Ontvanger markeerde als spam — verwijder uit alle lijsten
      await db.suppressions.create({ email: event.data.to, reason: "complaint" })
      break

    case "email.opened":
      console.log("Geopend door:", event.data.to)
      break
  }
}

Lokaal testen met ngrok

  1. Installeer ngrok: npm install -g ngrok
  2. Start een tunnel: ngrok http 3000
  3. Kopieer de https://xxxx.ngrok.io-URL
  4. Vul die in als webhook-URL in het Wesender-dashboard

Tips

  • Geef altijd snel een 200-respons terug — Wesender probeert anders tot 5 keer opnieuw
  • Sla events op in een database voor auditing — zo mis je nooit een event
  • Verwerk zware taken asynchroon (bijv. via een wachtrij) na de 200-respons
  • Gebruik idempotente verwerking — hetzelfde event kan meerdere keren binnenkomen

Foutoplossing

  • 401 Ongeldige handtekening — zorg dat je de raw body gebruikt voor HMAC-berekening, niet de geparseerde JSON
  • Webhook niet ontvangen — controleer of je endpoint publiek bereikbaar is (niet localhost)
  • Geen events — ga naar het dashboard en controleer of de webhook actief is en de juiste events geselecteerd zijn