Verify webhook signatures
Verify the raw payload and delivery timestamp before accepting a Koneth webhook.
Koneth signs each delivery attempt using the endpoint’s signing secret. Use a different secret for each endpoint and keep it in your backend’s secret store.
What is signed
Section titled “What is signed”The signature is the hexadecimal HMAC-SHA256 digest of:
timestamp + "." + raw_request_bodytimestamp is the exact X-Koneth-Timestamp header string, expressed in Unix seconds. The secret is the full webhook signing secret. The digest arrives in X-Koneth-Signature as v1=<64 hexadecimal characters>.
Node.js verification example
Section titled “Node.js verification example”This function verifies a raw Buffer. It does not start a receiver, create a queue, or validate the event’s business fields. Pass the secret associated with the receiving endpoint, not a secret chosen by an unverified field in the request.
import { Buffer } from "node:buffer";import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyKonethWebhook({ rawBody, timestamp, signature, secret, nowSeconds = Math.floor(Date.now() / 1000),}) { if (!Buffer.isBuffer(rawBody)) throw new Error("Raw body required"); if (typeof secret !== "string" || secret.length === 0) { throw new Error("Webhook secret is not configured"); } if (typeof timestamp !== "string" || !/^[0-9]{1,12}$/.test(timestamp)) { throw new Error("Invalid delivery timestamp"); } if (typeof signature !== "string" || !/^v1=[0-9a-f]{64}$/.test(signature)) { throw new Error("Invalid signature format"); }
const sentAt = Number(timestamp); // This receiver chooses a five-minute delivery-time tolerance. if (!Number.isSafeInteger(sentAt) || !Number.isSafeInteger(nowSeconds) || Math.abs(nowSeconds - sentAt) > 300) { throw new Error("Delivery timestamp outside tolerance"); }
const expected = createHmac("sha256", secret) .update(timestamp + ".", "utf8") .update(rawBody) .digest(); const received = Buffer.from(signature.slice(3), "hex"); if (received.length !== expected.length || !timingSafeEqual(received, expected)) { throw new Error("Invalid webhook signature"); }
return JSON.parse(rawBody.toString("utf8"));}The code uses Node’s HMAC and timing-safe comparison APIs. The strict hexadecimal check prevents malformed or truncated signatures from being accepted by the decoder.
The five-minute tolerance is an example receiver policy, not an expiry rule for trading events. Keep the receiver clock synchronized. Automatic retries receive a fresh delivery timestamp and signature even when the event itself is older; do not reject a valid retry merely because its data.measuredAt is old.
Accept an event safely
Section titled “Accept an event safely”- Read a bounded raw body
Limit request-body size and request duration in your HTTP server. Preserve the original bytes. Reject missing or ambiguous signature headers.
- Verify and validate
Run signature verification, then validate
version,type, and the expected payload fields. For the current metric event, require a validdata.eventIdand check it equalsX-Koneth-Event-Idif you use that header. The event-ID header is not independently signed; the body is. - Persist once
Durably store the verified event and enqueue its processing atomically. Enforce uniqueness on your receiving endpoint and the signed event ID. If the same ID arrives with different verified content, flag it for investigation rather than overwriting the original.
- Acknowledge and process
Return
2xxafter durable acceptance or recognition of an already accepted duplicate. If durable storage is unavailable, return a non-2xxresponse so delivery can retry. Apply business actions asynchronously with their own duplicate protection.
Do not return success before saving an event you still need: a successful acknowledgement stops automatic retries for that delivery. Do not hold the connection open while fetching every account record or evaluating long-running rules.
Receiver test checklist
Section titled “Receiver test checklist”Before activation, verify that your receiver:
- Accepts a correctly signed payload and rejects an incorrect secret.
- Rejects a changed body, changed timestamp, malformed signature, and timestamps outside its tolerance.
- Rejects a changed event-ID header when it differs from the signed body’s ID.
- Handles a fresh, valid delivery signature for an older event.
- Persists concurrent duplicates once and processes them without duplicate financial or account actions.
- Recovers after a crash between durable acceptance and processing.
- Treats out-of-order events as historical data rather than replacing newer state.
Endpoint registration and activation are separate from signature verification. Contact Koneth if you need activation or signing-secret recovery; the current Console does not provide a complete self-service flow. See Webhooks.