Instead of polling, pass webhook_url on submit and the completed payload is POSTed to it,
signed. Omit webhook_url and your account’s default endpoint is used, if one is set
(Dashboard → Webhooks); omit it with no default set and nothing is delivered — poll instead.
The endpoint is resolved when the batch is accepted, so changing your default does not
retarget a batch already submitted. Results always remain fetchable via GET, so a failed
delivery never loses them.
The delivery body is the same SignalsResponse the GET
endpoint returns. Deliveries are signed, retried with backoff, and dead-lettered on exhaustion.
Verifying the signature
Every delivery carries an X-TruthFactor-Signature header. Its value is the HMAC-SHA256 of
the exact raw request-body bytes, keyed by your account’s webhook signing secret,
lowercase hex-encoded and prefixed sha256=:
Recompute the same value over the body you received and compare the two in constant time.
Verify against the raw bytes before you parse or re-serialise the JSON — a re-serialised
body will not reproduce the signature.
Test vector
Check your implementation against this before going live:
Managing deliveries
Dashboard → Webhooks is where you set the default endpoint, reveal or rotate the signing
secret, and read the delivery log — every attempt for every batch, its response status, when
the next retry is due, and which deliveries dead-lettered. A batch you’ve fixed the receiver
for can be re-sent from there; the replay is recorded as a further attempt and erases none of
the failed ones.
Revealing the secret mints one if your account has none yet, so you can wire verification up
before your first delivery. Rotating takes effect immediately — a signature computed with the
old secret stops verifying.