Skip to main content
Every webhook is signed with your endpoint’s secret. Check the signature on the raw request body, before parsing it: a re-serialised body will not match. The header looks like t=1762682980,v1=5f2c.... Each v1 is the hex HMAC-SHA256 of "<t>.<raw body>" with a signing secret. Accept the request if any v1 matches your secret: for 24 hours after you rotate a secret, the header carries one v1 for the new secret and one for the old. Reject a timestamp more than five minutes from your clock, so a captured request cannot be replayed later.

Node

With Express, read the body raw for this route:

Python

In Flask, pass request.get_data() as raw_body and request.headers.get("OmniChar-Signature", "") as header. Answer quickly with a 2xx and do slow work afterwards: a request that takes more than 10 seconds counts as failed and is retried.