Axiym

Verifying signatures

View Markdown

Axiym signs the raw HTTP request body. Verify the signature before trusting or processing the event.

Do not parse and re-serialize JSON for verification. Whitespace, field order, or encoding changes produce different bytes and cause verification to fail.

Verification steps

  1. Capture the raw body bytes before JSON parsing.
  2. Read X-Signature, X-Key-Id, and X-Algorithm.
  3. Reject the request unless X-Algorithm is Ed25519.
  4. Load the cached public key for X-Key-Id, or retrieve it from the API.
  5. Verify the base64 signature against the raw body.
  6. Parse the JSON only after verification succeeds.
  7. De-duplicate by the event id.

Return a non-2xx response when verification fails.

JavaScript example

import { createPublicKey, verify } from "node:crypto";

export function verifyAxiymWebhook(rawBody, headers, keyResponse) {
  const signature = headers["x-signature"];
  const keyId = headers["x-key-id"];
  const algorithm = headers["x-algorithm"];

  if (!signature || !keyId || algorithm !== "Ed25519") return false;
  if (keyResponse.publicKeyId !== keyId) return false;

  const publicKey = createPublicKey({
    key: Buffer.from(keyResponse.publicKey, "base64"),
    format: "der",
    type: "spki",
  });

  return verify(
    null,
    rawBody,
    publicKey,
    Buffer.from(signature, "base64"),
  );
}

Operational checks

  • Cache keys by publicKeyId so signing-key rotation is safe.
  • If enforcing a maximum event age, allow for delivery delays and retries as well as clock skew. The event timestamp is unchanged across delivery attempts; it is not the time of the latest attempt. Agree an acceptable window with Axiym rather than assuming a short clock-skew limit.
  • Persist event IDs so de-duplication survives restarts.
  • Return 2xx only after durable persistence or queueing.
  • Fetch the current payout if ordering or current state matters.