Webhooks
How Talkif delivers webhooks to your endpoints, and how to verify they genuinely came from Talkif.
Talkif sends a webhook to your server whenever a flow function calls your HTTP endpoint during a call. So you can trust those requests, Talkif signs each one — proving it came from Talkif and wasn't tampered with in transit. The signature travels in the Talkif-Signature header, and verifying it is strongly recommended for any endpoint that takes action on the request.
Signing is per account and opt-in: requests are signed only once you've created a signing secret. Until then, they're sent unsigned.
Get your signing secret
- Go to Developer → Credentials → Webhook signing.
- Click Create signing secret.
- Copy the secret. It's shown once — it can't be retrieved later.
The secret looks like whsec_ followed by 43 URL-safe characters (49 characters total).
Use the secret exactly as shown, including the whsec_ prefix, as the raw HMAC key. Do not strip the prefix and do not base64-decode it. Some providers (e.g. Svix) base64-decode the part after whsec_ — Talkif does not. A verifier that decodes the secret will fail every check.
The signature header
Talkif-Signature: t=1718983045,v1=3f9a...c2| Component | Meaning |
|---|---|
t | Unix timestamp (seconds) when the signature was generated |
v1 | Lowercase hex HMAC-SHA256 of the signed payload. There may be more than one v1 during secret rotation — accept the request if any matches. |
How the signature is computed
signed_payload = "{t}.{body}"
v1 = lowercase_hex( HMAC_SHA256(secret, signed_payload) ){t}is the timestamp from the header.{body}is the exact raw bytes of the request body as received — do not parse and re-serialize it (key order and whitespace must match byte-for-byte).secretis your fullwhsec_…value, used verbatim as the UTF-8 HMAC key.
Verifying a request
- Read the
Talkif-Signatureheader and parse outtand everyv1. - (Recommended) Reject the request if
tis too old — this limits replay. A 5-minute tolerance is typical. - Compute
HMAC_SHA256(secret, "{t}.{rawBody}")as lowercase hex. - Compare it against each
v1with a constant-time comparison. Accept if any matches.
Node.js
const crypto = require("crypto");
function verifyTalkifWebhook(rawBody, signatureHeader, secret) {
let t;
const v1s = [];
for (const part of signatureHeader.split(",")) {
const [key, value] = part.split("=");
if (key === "t") t = value;
else if (key === "v1") v1s.push(value);
}
if (!t || v1s.length === 0) return false;
// Replay protection (recommended)
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const expected = crypto
.createHmac("sha256", secret) // key = the full "whsec_..." string
.update(`${t}.${rawBody}`, "utf8")
.digest("hex");
return v1s.some(
(v1) =>
v1.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected)),
);
}Python
import hashlib, hmac, time
def verify_talkif_webhook(raw_body: bytes, signature_header: str, secret: str) -> bool:
t, v1s = None, []
for part in signature_header.split(","):
key, _, value = part.partition("=")
if key == "t":
t = value
elif key == "v1":
v1s.append(value)
if not t or not v1s:
return False
# Replay protection (recommended)
if abs(time.time() - int(t)) > 300:
return False
signed_payload = t.encode() + b"." + raw_body
expected = hmac.new(secret.encode(), signed_payload, hashlib.sha256).hexdigest()
return any(hmac.compare_digest(v1, expected) for v1 in v1s)Verify against the raw body bytes, not a re-encoded copy. Frameworks that auto-parse JSON and hand you a re-serialized string will change key order or spacing and break the signature. Capture the raw body before parsing.
Empty-body requests
For requests with no body — non-body methods (GET, HEAD) or a body that is an empty object {} — Talkif sends no body and signs over an empty body. The signed payload is "{t}." (the timestamp, a dot, then nothing), not "{t}.{}". The verifiers above handle this automatically when the raw body is an empty string/bytes.
Rotating the secret
Rotating issues a new secret while keeping the old one valid for an overlap window, so in-flight integrations don't break.
- In Developer → Credentials → Webhook signing, click Rotate.
- Copy the new secret and deploy it to your endpoint.
During the overlap, Talkif signs each request with both secrets and includes a v1 for each:
Talkif-Signature: t=1718983045,v1=<new>,v1=<old>Because a correct verifier accepts the request if any v1 matches, both your old and newly deployed code keep working until the window closes. After it closes, only the new secret is sent.