Skip to main content
Every webhook endpoint has a secret. When the endpoint is signed, every call we make to it carries a signature of the request body, computed with that secret. Check the signature before you act on a call: it proves the call came from Platnova and that the body was not changed on the way.

The headers we send

Which endpoints are signed. Every endpoint created on or after 24 September 2026 is signed. Endpoints created before that date are not signed unless we have switched signing on for you. Until then they receive only the plain-text X-Webhook-Secret and X-Auth-Secret headers, with no end date. To have an older endpoint signed, contact support, or delete it and create it again.
X-Platnova-Signature always holds exactly one sha256= value. Compare the whole header with the value you compute. Do not split it on commas or look for a list. During a rotation the old secret’s signature comes in its own header, X-Platnova-Signature-Previous.

Verifying a signature

  1. Read the raw request body as bytes, before any JSON parsing. Do not parse the JSON and serialise it again: any difference in spacing, key order or escaping changes the signature, and verification fails even though the call is genuine.
  2. Compute "sha256=" + hex(HMAC-SHA256(key = your secret as UTF-8, message = raw body)), using lowercase hex.
  3. Compare it with X-Platnova-Signature in constant time, using one of the functions below, never ==.
  4. If they match, accept the call. During a rotation, also accept a match on X-Platnova-Signature-Previous.
  5. If neither matches, answer with a non-2xx status, such as 401. See Answer honestly.
You can check your implementation against a known value. With the secret secret and the body {"event":"webhook.test"} (24 bytes, no trailing newline), the signature is:

Answer honestly

We judge a delivery only by your HTTP status code. Any 2xx counts as delivered, and that event is never sent again. We cannot see what your code decides after it answers. So if your verification fails, answer with a non-2xx status (for example 401). If you answer 200 with something like {"received": true, "verified": false}, we record the event as delivered and stop retrying. Your system has then dropped the event, and neither side gets any warning. A non-2xx answer gets the event retried, shows up as a failure in your dashboard, and, if it keeps happening, pauses the endpoint and emails you.

Key id

X-Platnova-Key-Id names the secret that produced the signature. It is the first 8 hex characters of SHA-256 of the secret, so it identifies a secret without revealing it. It is not secret itself, and it is safe to log. Use it when you hold more than one Platnova secret. For example, two Platnova businesses might post to one URL, or you might be part-way through a rotation. Look up the secret by key id, then verify with that secret. Do not try each secret in turn. To work out the key id of a secret you hold:
The dashboard and GET /v1/webhooks show each endpoint’s key_id (and previous_key_id during a rotation), so you can match what you hold against what we send.

Rotating the secret

Rotate an endpoint’s secret from the dashboard: Settings → Webhooks → the endpoint’s menu → Rotate secret. The dashboard calls POST /v1/webhooks/{id}/rotate-secret, which needs a signed-in session with permission to update webhooks; it cannot be called with an API key. The response carries the new secret. It is shown once. We never return a secret again, so store it before you close the dialog.
During the overlap window, every signed delivery carries both signatures:
  • X-Platnova-Signature and X-Platnova-Key-Id, computed with the new secret.
  • X-Platnova-Signature-Previous and X-Platnova-Key-Id-Previous, computed with the old secret.
To rotate without missing an event:
  1. Rotate, and store the new secret beside the old one.
  2. Accept a call if either signature verifies against a secret you hold.
  3. Switch your receiver to the new secret.
  4. Once previous_secret_expires_at has passed, the -Previous headers stop arriving. Delete the old secret.
Rotating again during an overlap window drops the oldest secret. Only the current secret and the one before it are ever honoured.
Unsigned endpoints have no overlap. An endpoint that is not signed only receives the secret in plain text, in X-Webhook-Secret and X-Auth-Secret. After a rotation those headers carry the new secret straight away. Update your receiver first, or ask us to sign the endpoint.
When you create an endpoint, the response also returns its secret once. That is the secret you supplied, or, if you left it blank, the one we generated.

Sending a test event

To check your receiver end to end, use Send test event in the endpoint’s menu. The dashboard calls POST /v1/webhooks/{id}/test, which sends this body straight away, signed exactly like a real delivery:
It waits for your answer and returns what happened: A test event is not stored as an event. It does not appear in your event history, and it never counts towards pausing the endpoint.
delivered: true means the call reached your server and your server answered 2xx. It does not mean your verification passed. If your code answers 200 and then treats the call as unverified, we cannot tell. Check response_body, and answer non-2xx when verification fails.