Webhooks
Tenka tells your system when something happens.
Register the URL under Settings › Webhooks with a secret: each delivery is signed in X-Tenka-Signature (HMAC-SHA256 of the body) and retried if your system does not answer 2xx. The events:
The events
| Name | Description |
|---|---|
fatura.paga | Invoice paid (payment confirmed) |
proposta.aceita | Sales proposal accepted (by the customer) |
negocio.ganho | Deal won → contract created |
demanda.criada | Ticket created |
lead.criado | New lead in the CRM |
remessa.criada | Remessa de rolos aberta para o beneficiador (API) |
apontamento.criado | Apontamento de produção lançado (API) |
Headers of each delivery
| Header | Description |
|---|---|
X-Tenka-Event | The event (one of the list above). |
X-Tenka-Delivery | Unique delivery identifier: use it to avoid processing twice. |
X-Tenka-Signature | sha256= followed by the hex HMAC-SHA256 of the raw body with the subscription secret. |
X-Tenka-Event-Version | Optional: contract version of the event body. |
X-Tenka-Replay-Of | Optional: present only when the delivery is a resend; carries the original delivery id. |
How to verify the signature
Compute the HMAC-SHA256 of the body exactly as received (before parsing), with the subscription's whsec_ secret, and compare with the header in constant time. Mismatch: answer 401 and discard.
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);Answer 2xx within a few seconds and process later; without 2xx the delivery is retried.