Webhook Security

Webhook Security

Knova sends webhook notifications as POST requests to each active subscription URL. The headers below protect notifications Knova sends to you. To protect your own calls to Knova, see API Authentication.

Each request can carry two independent security headers:

HeaderPurpose
x-signatureProves the body came from your Knova deployment and was not altered in transit
x-api-keyShared secret sent by Knova, which you can check before doing any other work

Both are enabled per deployment, not per subscription, and apply to every subscription in that deployment. Ask your Knova contact which headers are enabled for your deployment.

Request shape

POST <subscription url>
Content-Type: application/json
x-signature: <base64 signature of the body>
x-api-key: <shared secret>

{"id":"…","uetr":"…","event_type":"Final","created":"2026-01-01T00:00:00Z","payload":{…}}
FieldMeaning
idEvent ID. Stays the same across retries and resends, so use it to deduplicate.
uetrUnique end-to-end transaction reference, or null.
event_typeOne of Initial, DigitalTwin, Onchain, Fiat, Final, and ApprovalRequest when approvals are enabled.
createdTime this delivery attempt was built. It changes on every attempt, so the body and its signature change between retries.
payloadEvent data. Its shape depends on event_type.

x-signature: payload signing

What is signed

The signature covers the exact raw request body bytes, with no prefix, timestamp, or nonce. Verify against the raw bytes as received. If you parse and re-serialize the JSON first, key order or whitespace can change and verification will fail.

Algorithm and encoding

The scheme depends on the type of the key Knova signs with:

Key typeSignature schemeHash
RSARSASSA-PKCS1-v1_5SHA-512
EC (e.g. P-256 / prime256v1)ECDSA, DER-encoded signatureSHA-512
Ed25519Ed25519 (pure, no pre-hash)n/a

The signature is encoded as standard base64 with padding (not URL-safe).

Public key

Knova gives you the public key in PEM format out of band; no endpoint serves it. Requests do not carry a key ID, so during a key rotation accept signatures from both the old and new public keys until the change is complete.

Verifying a signature

  1. Read the raw request body bytes before parsing them.
  2. Base64-decode the x-signature header.
  3. Verify the signature over the raw body with the Knova public key, using the scheme for its key type.
  4. Reject the request (for example with 401) if the header is missing or verification fails. Only then parse the JSON.
  5. Deduplicate on id, and reject events whose created is too old.

Example with openssl:

echo -n "$X_SIGNATURE" | base64 -d > signature.bin
# body.json must contain the exact bytes received

# RSA or EC
openssl dgst -sha512 -verify knova_public_key.pem -signature signature.bin body.json

# Ed25519
openssl pkeyutl -verify -pubin -inkey knova_public_key.pem -rawin -in body.json -sigfile signature.bin

x-api-key: shared secret

When enabled, every request carries an x-api-key header with a secret value agreed with Knova. This is a secret Knova sends to you. It is not the API key you send to Knova on API calls.

  • Scope: one value per deployment, sent to every subscription.
  • Checking it: compare the header with the expected value using a constant-time comparison, and reject the request if it is missing or does not match.
  • Rotation: accept both the old and new values while the change rolls out.

The API key only shows that the caller knows a shared secret. It does not protect the body from tampering or replay. Use it together with x-signature, not instead of it.

Delivery behavior that affects verification

  • Success: only HTTP 200 counts as delivered. Any other status, including other 2xx codes, is retried.
  • Retries: failed deliveries are retried with exponential backoff, up to 512 seconds between attempts.
  • Re-signing: created changes on every attempt, so each retry has a new body and signature. Deduplicate on id, not on the signature.
  • Resends: a notification can be resent on request. A resend carries the same id.

Did this page help you?