Webhooks
Când trimiți un job batch cu options.callback_url (planuri cu webhooks), posty.ro face POST cu o notificare JSON la acel URL când jobul se termină. Verifică headerul X-Posty-Signature înainte să ai încredere în payload.
batch.completed
Jobul batch s-a terminat. Descarcă rezultatele de la results_url (păstrate 30 de zile).
{
"event": "batch.completed",
"batch_id": "bat_550e8400e29b4123",
"status": "done",
"row_count": 1200,
"processed": 1200,
"results_url": "https://api.posty.ro/v1/addresses/batch/bat_550e8400e29b4123/results",
"finished_at": "2026-10-14T12:00:00Z"
}batch.failed
Jobul batch a eșuat. error_message descrie cauza.
{
"event": "batch.failed",
"batch_id": "bat_550e8400e29b4123",
"status": "failed",
"row_count": 1200,
"processed": 310,
"results_url": "https://api.posty.ro/v1/addresses/batch/bat_550e8400e29b4123/results",
"finished_at": "2026-10-14T12:00:00Z",
"error_message": "..."
}Verificarea semnăturii
Fiecare notificare poartă headerul X-Posty-Signature: t=<unix>,v1=<hex>. v1 este HMAC-SHA256 al șirului "<t>." urmat de corpul brut al cererii. Recalculează-l, compară în timp constant și respinge timestamp-urile vechi (de ex. peste 5 minute) ca să blochezi replay-urile. Răspunde cu 2xx; orice alt status este tratat ca eșec.
import { createHmac, timingSafeEqual } from 'node:crypto'
// header = req.headers['x-posty-signature'], rawBody = the exact bytes received
function verifyPostySignature(header, rawBody, secret, toleranceSec = 300) {
const parts = Object.fromEntries(header.split(',').map(p => p.split('=')))
const t = Number(parts.t)
if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false
const expected = createHmac('sha256', secret).update(`${parts.t}.`).update(rawBody).digest('hex')
const got = Buffer.from(parts.v1 ?? '', 'hex')
return got.length === 32 && timingSafeEqual(got, Buffer.from(expected, 'hex'))
}