Zuletzt aktualisiert: 2026-03-03•6 Min. Lesezeit

Webhooks

Truncus sendet Zustellereignisse in Echtzeit an Ihren Endpoint.

Event-Format

{
  "event_id": "<event id>",
  "event_type": "delivered",
  "timestamp": "2026-10-17T09:30:00.000Z",
  "email": {
    "id": "<email id>",
    "to": "user@example.com",
    "subject": "Welcome!",
    "status": "delivered"
  },
  "data": {}
}

event_type ist der Typ des Ereignisses, zum Beispiel delivered, bounce, complaint oder failed. email.status ist der Status der E-Mail beim Senden des Webhooks. data enthält die Details des Ereignisses und hängt vom Ereignistyp ab. Wenn Sie für einen Endpoint Ereignistypen ausgewählt haben, werden nur diese gesendet.

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

Request-Header

Content-Type: application/json
X-Truncus-Signature: <64 hex characters>
X-Truncus-Event-Id: <event id>
X-Truncus-Delivery-Id: <delivery id>

Test-Events, die im Dashboard mit „Send test event“ gesendet werden, tragen X-Truncus-Test: true statt X-Truncus-Delivery-Id und "test": true im Body.

Ihr Endpoint muss innerhalb von 30 Sekunden mit einem HTTP-2xx-Status antworten. Jede andere Antwort oder ein Timeout löst eine Wiederholung aus.

Signatur-Verifizierung

Alle Webhook-Anfragen sind mit HMAC-SHA256 und dem Signing-Secret Ihres Endpoints signiert. Verifizieren Sie jede eingehende Anfrage.

Der Header X-Truncus-Signature enthält den hex-kodierten HMAC-SHA256 des rohen Request-Bodys. Es gibt keinen Timestamp und kein Präfix:

hex(HMAC_SHA256(webhook_secret, raw_body))

Node.js-Verifizierung:

import crypto from 'crypto'

function verifyWebhookSignature(
  secret: string,
  rawBody: string,
  signature: string
): boolean {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody, 'utf8')
    .digest('hex')

  const a = Buffer.from(signature, 'hex')
  const b = Buffer.from(expected, 'hex')
  return a.length === b.length && crypto.timingSafeEqual(a, b)
}

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

Ereignisse deduplizieren

Eine Wiederholung sendet dasselbe Ereignis mit derselben X-Truncus-Event-Id erneut. Speichern Sie die verarbeiteten Event-IDs und überspringen Sie bereits gesehene:

const eventId = req.headers['x-truncus-event-id'] as string

if (await alreadyProcessed(eventId)) {
  return res.status(200).send('ok')
}
// ... handle the event, then:
await markProcessed(eventId)

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', async (req, res) => {
  const signature = (req.headers['x-truncus-signature'] as string) ?? ''
  const eventId = req.headers['x-truncus-event-id'] as string
  const rawBody = req.body.toString('utf8')

  // Signaturverifizierung
  const expected = crypto
    .createHmac('sha256', process.env.TRUNCUS_WEBHOOK_SECRET!)
    .update(rawBody, 'utf8')
    .digest('hex')
  const a = Buffer.from(signature, 'hex')
  const b = Buffer.from(expected, 'hex')
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
    return res.status(401).send('Ungültige Signatur')
  }

  // Deduplizieren: eine Wiederholung trägt dieselbe Event-ID
  if (await alreadyProcessed(eventId)) {
    return res.status(200).send('ok')
  }

  const event = JSON.parse(rawBody)

  if (event.event_type === 'bounce') {
    // Zustellungsdatensatz aktualisieren — event.data enthält Details
  }

  if (event.event_type === 'complaint') {
    // Adresse unterdrücken — nicht erneut senden
  }

  await markProcessed(eventId)
  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

Wenn Ihr Endpoint keine 2xx-Antwort liefert, wiederholt Truncus die Zustellung. Jede Verzögerung zählt ab dem vorherigen Versuch:

VersuchVerzögerung
1Sofort
25 Minuten
330 Minuten
42 Stunden
524 Stunden

Nach dem fünften fehlgeschlagenen Versuch wird die Zustellung als fehlgeschlagen markiert, etwa 26,5 Stunden nach dem ersten Versuch. Eine fehlgeschlagene Zustellung können Sie im Dashboard erneut auslösen. Ereignisse bleiben unbegrenzt wiederholbar.

Manuelle Wiederholung

Um die gespeicherten Ereignisse einer E-Mail erneut an Ihre Webhooks zu senden, rufen Sie den Sandbox-Replay-Endpoint mit einem API-Key mit dem Scope send auf:

POST /api/v1/sandbox/replay
Content-Type: application/json

{ "source_message_id": "<email id>" }

Truncus erstellt eine Sandbox-Kopie der E-Mail und sendet jedes ihrer gespeicherten Ereignisse erneut an Ihre Webhooks. Die E-Mail wird nicht neu gesendet. Wiederholte Ereignisse erhalten neue Event-IDs und tragen "replay": true in data.

Webhooks