Skip to main content
You manage webhooks from the developer dashboard. Open your workspace’s Webhooks page (Admin role or higher) to add an endpoint, copy its signing secret, send a test event, read the delivery log, replay a delivery that failed, and rotate the secret. The secret is shown once, when the endpoint is created or rotated — copy it then. Outside Production, only test-mode endpoints on a Sandbox-plan workspace are ever called; a live endpoint is never called outside production.
Every webhook is signed, so you can confirm it came from ClinikEHR and was not altered or replayed by someone else, before you act on it.

The headers

Each delivery carries three headers:

What gets signed

The signature covers the exact bytes of the id, the timestamp, and the raw request body, joined by periods:
Use the raw body — not a re-serialized version of the parsed JSON, which can byte-for-byte differ from what was sent (key order, whitespace) and would make a correct signature look wrong.

Computing the signature

Your endpoint’s signing secret looks like whsec_ followed by 48 lowercase hex characters. Decode the hex characters to their raw bytes first — the secret is not used as a literal string.
Compute an HMAC-SHA256 of signed_content using key_bytes, base64-encode the result, and prefix it with v1,. Compare it against each value in webhook-signature (there may be more than one during a secret rotation — see below) using a constant-time comparison, never a plain ===/==.

Also check the timestamp

Reject a delivery whose webhook-timestamp is too far in the past (a replayed request) or the future (a clock skew you can’t explain) — five minutes either way is a reasonable window. This is a separate check from the signature itself; a valid signature on an old, replayed request is still a replay.

Secret rotation — why you may see two signatures

Rotating an endpoint’s secret does not invalidate deliveries in flight. For the rotation’s overlap window (24 hours by default), webhook-signature carries two v1,... values, one for the outgoing secret and one for the new one, space-separated in the single header. Verify against a signature that matches either — never require both.

See also