Integraties

Webhooks

Webhooks sturen een HTTP POST naar jouw server zodra er iets met een e-mail gebeurt: bezorging, bounce, open of spamklacht. Zo hoef je niet te pollen en reageer je direct.

Webhook aanmaken

POST https://api.wesender.nl/webhooks
VeldTypeVereistBeschrijving
url string Ja HTTPS-eindpunt dat de POST-verzoeken ontvangt.
events string[] Ja Lijst van te ontvangen event-types. Zie hieronder.
secret string Nee Geheim voor handtekeningverificatie. Wij genereren er één als je het weglaat.
curl -X POST https://api.wesender.nl/webhooks \
  -H "Authorization: Bearer $WS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url":    "https://joudomein.nl/webhooks/email",
    "events": ["email.delivered", "email.bounced", "email.complained"]
  }'

Event types

EventWanneer
email.delivered De ontvangende mailserver heeft de e-mail geaccepteerd.
email.bounced Bezorging definitief mislukt (hard bounce) of tijdelijk mislukt (soft bounce).
email.opened Ontvanger heeft de e-mail geopend (vereist open-tracking).
email.clicked Ontvanger heeft op een link geklikt (vereist click-tracking).
email.complained Ontvanger heeft de e-mail als spam gemarkeerd.

Payload: email.delivered

POST jouw endpoint
{
  "event":     "email.delivered",
  "created_at":"2026-06-08T14:23:11Z",
  "data": {
    "email_id":   "em_01hwxxxxxxxxxxxxxx",
    "from":       "noreply@joudomein.nl",
    "to":         "klant@voorbeeld.nl",
    "subject":    "Je bestelling is verzonden",
    "tags":       { "flow": "checkout" }
  }
}

Payload: email.bounced

Het bounce_type veld is hard (permanent, automatisch gesupprimeerd) of soft (tijdelijk).

POST jouw endpoint
{
  "event":     "email.bounced",
  "created_at":"2026-06-08T14:25:03Z",
  "data": {
    "email_id":    "em_01hwxxxxxxxxxxxxxx",
    "from":        "noreply@joudomein.nl",
    "to":          "ongeldig@voorbeeld.nl",
    "bounce_type": "hard",
    "reason":      "550 5.1.1 The email account does not exist"
  }
}

Handtekeningverificatie

Elk webhook-verzoek bevat een x-wesender-signature header: een HMAC-SHA256 hash van de ruwe request body, ondertekend met jouw webhook secret. Controleer dit altijd om nep-verzoeken te voorkomen.

import crypto from "crypto"

// In je Express/Hono/Next.js route handler:
function verifyWebhook(rawBody: string, signature: string, secret: string): boolean {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex")
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected),
  )
}

// Gebruik:
const sig    = request.headers["x-wesender-signature"] as string
const isValid = verifyWebhook(rawBody, sig, process.env.WS_WEBHOOK_SECRET!)

Compleet handler voorbeeld

// Next.js App Router voorbeeld
import { NextRequest, NextResponse } from "next/server"

export async function POST(req: NextRequest) {
  const rawBody  = await req.text()
  const sig      = req.headers.get("x-wesender-signature") ?? ""

  if (!verifyWebhook(rawBody, sig, process.env.WS_WEBHOOK_SECRET!)) {
    return NextResponse.json({ error: "Invalid signature" }, { status: 401 })
  }

  const event = JSON.parse(rawBody)

  switch (event.event) {
    case "email.delivered":
      // Markeer als bezorgd in je DB
      break
    case "email.bounced":
      if (event.data.bounce_type === "hard") {
        // Verwijder adres uit je mailinglijst
      }
      break
    case "email.complained":
      // Verwijder direct: dit is een spamklacht
      break
  }

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

Herlevering en fouttolerantie

  • Je endpoint moet binnen 10 seconden antwoorden met HTTP 2xx.
  • Bij een timeout of non-2xx proberen wij het tot 5 keer opnieuw, met exponentieel wachten.
  • Na 5 pogingen wordt het event gedropt en gelogd als mislukt in je dashboard.
  • Verwerk events asynchroon: antwoord snel, verwerk daarna.

Volgende stappen