Вебхуки
Tenka уведомляет вашу систему, когда что-то происходит.
Зарегистрируйте URL в Настройки › Вебхуки с секретом: каждая доставка подписана в X-Tenka-Signature (HMAC-SHA256 тела) и повторяется, если ваша система не отвечает 2xx. События:
Os eventos
| Nome | Descrição |
|---|---|
fatura.paga | Счёт оплачен (оплата подтверждена) |
proposta.aceita | Коммерческое предложение принято (клиентом) |
negocio.ganho | Сделка выиграна → договор создан |
demanda.criada | Создана заявка (тикет) |
lead.criado | Новый лид в CRM |
remessa.criada | Remessa de rolos aberta para o beneficiador (API) |
apontamento.criado | Apontamento de produção lançado (API) |
Cabeçalhos de cada entrega
| Cabeçalho | Descrição |
|---|---|
X-Tenka-Event | O evento (um dos da lista acima). |
X-Tenka-Delivery | Identificador único da entrega: use para não processar duas vezes. |
X-Tenka-Signature | sha256= seguido do HMAC-SHA256, em hexadecimal, do corpo cru com o segredo da inscrição. |
X-Tenka-Event-Version | Opcional: versão do contrato do corpo do evento. |
X-Tenka-Replay-Of | Opcional: presente só quando a entrega é um reenvio; traz o id da entrega original. |
Como conferir a assinatura
Calcule o HMAC-SHA256 do corpo exatamente como chegou (antes de converter para objeto), com o segredo whsec_ da inscrição, e compare com o cabeçalho em tempo constante. Não bateu: responda 401 e descarte.
Node (SDK)
import crypto from "node:crypto";
// raw = the request body exactly as received (Buffer)
function signatureOk(raw, header, secret) {
const expected = "sha256=" + crypto.createHmac("sha256", secret).update(raw).digest("hex");
return typeof header === "string" && header.length === expected.length
&& crypto.timingSafeEqual(Buffer.from(header), Buffer.from(expected));
}Python
import hashlib
import hmac
def signature_ok(raw: bytes, header: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(secret.encode(), raw, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, header or "")Java
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.HexFormat;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
static boolean signatureOk(byte[] raw, String header, String secret) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
String expected = "sha256=" + HexFormat.of().formatHex(mac.doFinal(raw));
return header != null && MessageDigest.isEqual(expected.getBytes(StandardCharsets.UTF_8), header.getBytes(StandardCharsets.UTF_8));
}C#
using System.Security.Cryptography;
using System.Text;
static bool SignatureOk(byte[] raw, string header, string secret)
{
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
var expected = "sha256=" + Convert.ToHexString(hmac.ComputeHash(raw)).ToLowerInvariant();
return header != null && CryptographicOperations.FixedTimeEquals(Encoding.UTF8.GetBytes(expected), Encoding.UTF8.GetBytes(header));
}PHP
<?php
$raw = file_get_contents("php://input");
$expected = "sha256=" . hash_hmac("sha256", $raw, getenv("TENKA_WEBHOOK_SECRET"));
if (!hash_equals($expected, $_SERVER["HTTP_X_TENKA_SIGNATURE"] ?? "")) {
http_response_code(401);
exit;
}
http_response_code(200);Responda 2xx em poucos segundos e processe depois; sem 2xx, a entrega é tentada de novo.