Integraties

Webhooks

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

Webhook aanmaken

Ga naar Dashboard → Webhooks en klik op Webhook toevoegen. Je kiest welke events je wilt ontvangen en krijgt direct een signatuurgeheim terug.

Je kunt ook via de API een 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.
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/wesender",
    "events": ["email.delivered", "email.bounced", "email.opened", "email.clicked", "email.failed"]
  }'

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). Bij een hard bounce wordt het adres automatisch gesupprimeerd.
email.opened Ontvanger heeft de e-mail geopend (vereist open-tracking pixel).
email.clicked Ontvanger heeft op een gevolgde link geklikt. De aangeklikte URL staat in data.click_url.
email.failed Tijdelijke bezorgfout — de e-mail kon niet worden afgeleverd maar is niet definitief gebounced.

Payload voorbeelden

email.delivered

POST jouw endpoint
{
  "type":       "email.delivered",
  "created_at": "2026-06-13T09:19:31Z",
  "data": {
    "id":      "av276ywjkg15k2ku9j1xgrqe",
    "from":    "noreply@joudomein.nl",
    "to":      ["klant@voorbeeld.nl"],
    "subject": "Je bestelling is verzonden",
    "tags":    { "flow": "checkout" }
  }
}

email.bounced

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

POST jouw endpoint
{
  "type":       "email.bounced",
  "created_at": "2026-06-13T09:21:07Z",
  "data": {
    "id":           "av276ywjkg15k2ku9j1xgrqe",
    "from":         "noreply@joudomein.nl",
    "to":           ["ongeldig@voorbeeld.nl"],
    "subject":      "Je bestelling is verzonden",
    "bounce_type":  "hard",
    "bounce_message": "550 5.1.1 The email account does not exist"
  }
}

email.opened

POST jouw endpoint
{
  "type":       "email.opened",
  "created_at": "2026-06-13T09:25:44Z",
  "data": {
    "id":      "av276ywjkg15k2ku9j1xgrqe",
    "from":    "noreply@joudomein.nl",
    "to":      ["klant@voorbeeld.nl"],
    "subject": "Je bestelling is verzonden"
  }
}

email.clicked

POST jouw endpoint
{
  "type":       "email.clicked",
  "created_at": "2026-06-13T09:26:01Z",
  "data": {
    "id":        "av276ywjkg15k2ku9j1xgrqe",
    "from":      "noreply@joudomein.nl",
    "to":        ["klant@voorbeeld.nl"],
    "subject":   "Je bestelling is verzonden",
    "click_url": "https://joudomein.nl/bestellingen/12345"
  }
}

email.failed

POST jouw endpoint
{
  "type":       "email.failed",
  "created_at": "2026-06-13T09:20:15Z",
  "data": {
    "id":      "av276ywjkg15k2ku9j1xgrqe",
    "from":    "noreply@joudomein.nl",
    "to":      ["klant@voorbeeld.nl"],
    "subject": "Je bestelling is verzonden"
  }
}

Handtekeningverificatie

Elk webhook-verzoek bevat de header Wesender-Signature met het formaat t=TIMESTAMP,v1=HEX. De handtekening is een HMAC-SHA256 van TIMESTAMP.BODY, ondertekend met jouw webhook-geheim. Controleer dit altijd om nep-verzoeken te voorkomen.

import crypto from "crypto"

/**
 * Verifieer de Wesender-Signature header.
 * Header formaat: t=TIMESTAMP,v1=HEX_HANDTEKENING
 */
function verifyWebhook(rawBody: string, signatureHeader: string, secret: string): boolean {
  const parts = Object.fromEntries(
    signatureHeader.split(",").map(p => p.split("=", 2) as [string, string])
  )
  const timestamp = parts["t"]
  const signature = parts["v1"]
  if (!timestamp || !signature) return false

  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex")

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected),
  )
}

// Gebruik:
const sig     = request.headers["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"
import crypto from "crypto"

function verifyWebhook(rawBody: string, signatureHeader: string, secret: string): boolean {
  const parts = Object.fromEntries(
    signatureHeader.split(",").map(p => p.split("=", 2) as [string, string])
  )
  const timestamp = parts["t"]
  const signature = parts["v1"]
  if (!timestamp || !signature) return false

  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex")

  return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
}

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

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

  const event = JSON.parse(rawBody)

  switch (event.type) {
    case "email.delivered":
      // Markeer als bezorgd in je database
      break
    case "email.bounced":
      if (event.data.bounce_type === "hard") {
        // Verwijder adres uit mailinglijst — permanent onbereikbaar
      }
      break
    case "email.opened":
      // Registreer open-tracking in je CRM
      break
    case "email.clicked":
      // Registreer klik op event.data.click_url
      break
    case "email.failed":
      // Tijdelijke bezorgfout — overweeg opnieuw versturen
      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 3 keer opnieuw (direct, na 1 s, na 5 s).
  • Na 3 pogingen wordt het event gedropt.
  • Verwerk events asynchroon: antwoord snel en verwerk daarna op de achtergrond.
  • Maak je handler idempotent — bij herlevering kan hetzelfde event meerdere keren binnenkomen.

Toekomstige events

Het webhooksysteem is ontworpen om eenvoudig uitgebreid te worden. Nieuwe events (zoals domeinverificatie of suppression-events) worden automatisch beschikbaar in het dashboard zodra ze actief zijn.

Volgende stappen