Skip to content

Webhooks ​

Webhooks avisam o seu sistema na hora em que algo acontece.

Cadastrar ​

No painel, Webhooks → Novo endpoint: informe uma URL https:// pública e escolha os eventos. Guarde o segredo (whsec_...) mostrado — ele serve para verificar a assinatura.

Eventos ​

EventoQuando
invoice.authorizedA NFS-e foi autorizada pelo Sistema Nacional (data traz a nota, com access_key e nfse_number)
invoice.rejectedO Sistema Nacional rejeitou a DPS (data.rejection traz código e mensagem)
invoice.cancelledA NFS-e foi cancelada
invoice.cancellation_rejectedO pedido de cancelamento foi recusado (data.cancellation_error)

Formato ​

http
POST /seu-endpoint
Content-Type: application/json
X-Proton-Event: invoice.authorized
X-Proton-Signature: t=1730000000000,v1=5f2b...
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-a3ce929d0e0e4736-01

{ "id": "evt_...", "type": "invoice.authorized", "created_at": 1730000000000, "tenant_id": 1, "data": { "id": "inv_...", "status": "authorized", "external_ref": "oficina:u3:os:812", "access_key": "3302700...", "nfse_number": "42" } }

Responda 2xx em até 10 s. Se falhar, tentamos de novo em 1 min, 5 min, 30 min, 2 h e 12 h. Use o id do evento para não processar duas vezes. Registre o traceparent nos seus logs: ele liga o evento à operação que o gerou.

Verificar a assinatura ​

Calcule HMAC-SHA256(segredo, "<t>.<corpo bruto>") em hexadecimal e compare com v1. Rejeite se t tiver mais de 5 minutos.

js
import crypto from 'node:crypto'

export function verify(secret, header, rawBody) {
  const { t, v1 } = Object.fromEntries(header.split(',').map((p) => p.split('=')))
  if (Math.abs(Date.now() - Number(t)) > 5 * 60_000) return false
  const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex')
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1))
}
python
import hashlib, hmac, time

def verify(secret: str, header: str, raw_body: bytes) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    if abs(time.time() * 1000 - int(parts["t"])) > 5 * 60_000:
        return False
    msg = parts["t"].encode() + b"." + raw_body
    expected = hmac.new(secret.encode(), msg, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])
apex
public static Boolean verify(String secret, String header, String rawBody) {
    Map<String, String> p = new Map<String, String>();
    for (String kv : header.split(',')) { p.put(kv.substringBefore('='), kv.substringAfter('=')); }
    Long t = Long.valueOf(p.get('t'));
    if (Math.abs(System.currentTimeMillis() - t) > 5 * 60 * 1000) return false;
    Blob mac = Crypto.generateMac('hmacSHA256', Blob.valueOf(p.get('t') + '.' + rawBody), Blob.valueOf(secret));
    return EncodingUtil.convertToHex(mac) == p.get('v1');
}
php
function verify(string $secret, string $header, string $rawBody): bool {
    parse_str(str_replace(',', '&', $header), $p);
    if (abs(round(microtime(true) * 1000) - (int)$p['t']) > 300000) return false;
    $expected = hash_hmac('sha256', $p['t'] . '.' . $rawBody, $secret);
    return hash_equals($expected, $p['v1']);
}