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'))
}

© 2026 posty.ro