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
| Versuch | Verzögerung |
|---|---|
| 1 | Sofort |
| 2 | 1 Minute |
| 3 | 5 Minuten |
| 4 | 15 Minuten |
| 5 | 1 Stunde |
| 6 | 6 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 →