Verifying webhooks¶
A callback_url receives an unauthenticated POST from the internet. Anything
that learns the URL can post to it, so a handler that trusts the body will
happily record an approval nobody gave.
Set a secret on the webhook in KirokuForms, pass the same string as
webhook_secret, and check every delivery.
from kirokuforms import KirokuFormsHITL, WebhookVerificationError
client = KirokuFormsHITL(api_key="…", webhook_secret="whsec_…")
@app.post("/kiroku-webhook")
def kiroku_webhook(request):
try:
payload = client.verify_webhook(
request.body, # raw bytes or str
request.headers.get("X-KirokuForms-Signature-256"),
)
except WebhookVerificationError as exc:
return Response(status=400, body=str(exc))
if payload["eventType"] == "hitl.task.completed":
answers = payload["data"]["submission"]["data"]
...
return Response(status=200)
How the signature works¶
KirokuForms computes:
hex encoded, and sends it two ways: in the X-KirokuForms-Signature-256 header,
and as a signature field inside the JSON body.
That means the signed bytes are not the bytes on the wire: the delivered body
has the signature added to it. Verifying requires parsing the JSON, dropping
signature, and re-serialising exactly the way JSON.stringify would.
verify_webhook does that, and its test suite checks it against signatures
generated by the actual server-side signer rather than by a Python
reimplementation agreeing with itself.
Pass the header when you have it. Omit it and the field inside the body is used, which is what you want in a framework that hands you parsed JSON and no headers.
Comparison is constant time. A wrong secret, a missing signature, a body that is
not JSON, or a body edited after signing all raise WebhookVerificationError.
Before 0.3.0
webhook_secret was accepted, documented as being for verification, and
read by nothing at all. If you built a callback endpoint from an earlier
README, it is not verifying anything. Add the call above.