Zuletzt aktualisiert: 2026-03-036 Min. Lesezeit

Webhooks

Truncus sendet Zustellereignisse in Echtzeit an Ihren Endpoint.

Event-Format

{
  "event": "email.delivery",
  "message_id": "msg_8f21",
  "status": "bounced",
  "reason": "mailbox_full",
  "timestamp": 1712345678
}

Events werden als HTTPS POST-Anfragen mit Content-Type: application/json gesendet.

Request-Header

Content-Type: application/json
Truncus-Signature: tcsig_abcd1234
Truncus-Timestamp: 1712345678

Ihr Endpoint muss mit HTTP 200 antworten. Nicht-200-Antworten lösen Wiederholungen aus.

Signatur-Verifizierung

Alle Webhook-Anfragen sind mit HMAC-SHA256 signiert. Verifizieren Sie jede eingehende Anfrage.

Signatur-Schema:

HMAC_SHA256(webhook_secret, timestamp + '.' + raw_body)

Node.js-Verifizierung:

import crypto from 'crypto'

function verifyWebhookSignature(
  secret: string,
  timestamp: string,
  rawBody: string,
  signature: string
): boolean {
  const payload = `${timestamp}.${rawBody}`

  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload, 'utf8')
    .digest('hex')

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

Verwenden Sie timingSafeEqual um Timing-Angriffe zu verhindern. Lehnen Sie die Anfrage ab, wenn die Signatur nicht übereinstimmt.

Replay-Schutz

Anfragen mit Timestamps älter als 5 Minuten ablehnen:

const now = Math.floor(Date.now() / 1000)
const ts = parseInt(req.headers['truncus-timestamp'], 10)

if (Math.abs(now - ts) > 300) {
  return res.status(400).send('Timestamp zu alt')
}

Vollständiges Handler-Beispiel

import express from 'express'
import crypto from 'crypto'

const app = express()
app.use(express.raw({ type: 'application/json' }))

app.post('/webhooks/truncus', (req, res) => {
  const signature = req.headers['truncus-signature'] as string
  const timestamp = req.headers['truncus-timestamp'] as string
  const rawBody = req.body.toString()

  const now = Math.floor(Date.now() / 1000)
  if (Math.abs(now - parseInt(timestamp, 10)) > 300) {
    return res.status(400).send('Timestamp zu alt')
  }

  const payload = `${timestamp}.${rawBody}`
  const expected = crypto
    .createHmac('sha256', process.env.TRUNCUS_WEBHOOK_SECRET!)
    .update(payload, 'utf8')
    .digest('hex')

  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
    return res.status(401).send('Ungültige Signatur')
  }

  const event = JSON.parse(rawBody)

  if (event.status === 'bounced') {
    // Zustellungsdatensatz aktualisieren
  }

  if (event.status === 'rejected') {
    // Adresse unterdrücken — nicht erneut versuchen
  }

  res.status(200).send('ok')
})

Wichtig: Als Raw-Bytes parsen

Den Request-Body als Raw-Bytes parsen, bevor JSON geparst wird. Die Signaturverifizierung muss auf den exakten empfangenen Bytes basieren.

Wiederholungsplan

VersuchVerzögerung
1Sofort
21 Minute
35 Minuten
415 Minuten
51 Stunde
66 Stunden

Maximales Wiederholungsfenster: 24 Stunden. Ereignisse bleiben unbegrenzt wiederholbar.

Manuelle Wiederholung

POST /v1/events/{event_id}/replay

Replay sendet das gespeicherte Ereignis erneut. Die E-Mail wird nicht neu gesendet. Siehe Replay →

Webhooks