Je hebt Javascript nodig om deze website te kunnen gebruiken. Pas je browserinstellingen in om verder te gaan!
Mutations

Callback signatures

Verify Mutation Engine callback signatures with the regional signing secret to reject altered, untrusted, and replayed requests.

Learn how to verify that requests to your callback endpoint are sent by the Mutation Engine.

It is important that your callback endpoint rejects requests from untrusted sources. You should verify the signature included with each callback to ensure it originated from the Mutation Engine, and have not been tampered with in transit

Overview

Mutation callbacks are delivered asynchronously via HTTP POST. Because callback URLs are user-provided and therefore untrusted, every callback request is cryptographically signed.

Signature verification allows you to confirm that:

  • The callback originated from the Mutation Engine
  • The request body and URL were not altered in transit
  • The request is not a replay of a previous callback

Whether or not you verify signatures in productions integrations is up to you, but we strongly recommend doing so.

Inspect the callback request

Each callback request has the following properties:

  • Method: POST
  • Body: JSON mutation-result payload.
  • Headers: Signature-related metadata, described below.

Callbacks use a max timeout of 15 seconds. If your server does not respond within that window, the Mutation Engine will throw an error and retry.

Signed Headers

Every callback request includes these HTTP headers:

  • x-mutationengine-timestamp
  • x-mutationengine-nonce
  • x-mutationengine-signature
  • x-mutationengine-sandbox-enabled

There is no key identifier header. All callbacks currently use a single region-specific signing secret.

What Is Signed

The signature covers the following inputs:

  • The callback URL path and query string.
  • The raw JSON request body.
  • The timestamp.
  • The nonce.

Any change to any of these values will invalidate the signature.

Signature Format

The signature header has the form:

x-mutationengine-signature: v2=<base64-hmac>

Where:

  • v2 is the signature version.
  • The value after v2= is a Base64-encoded HMAC-SHA256 digest.
  • The HMAC secret is your region-specific callback signing secret.

Retrieving Your Signing Secret

Your callback signing secret can be retrieved using the /auth/regions/me endpoint.

Signing secrets are region-scoped, not API-key–scoped. If you receive callbacks from multiple regions, you must verify them using the corresponding regional secret.

Canonical String

To verify a callback, you must reconstruct the canonical string-to-sign exactly as it was produced by the Mutation Engine.

<timestamp>
<nonce>
<path_and_query>
<body_sha256_hex>

Important details:

  • Fields are separated by newline characters (\n).
  • A trailing newline is required.
  • The canonical string has no version line.
  • <body_sha256_hex> is the lowercase hexadecimal SHA-256 hash of the raw JSON body.

Field Definitions

  • <timestamp>
    Value of x-mutationengine-timestamp (Unix time in milliseconds)
  • <nonce>
    Value of x-mutationengine-nonce (UUID v4)
  • <path_and_query>
    Callback URL path plus query string (e.g. /webhooks/engine-callback?foo=bar)
  • <body_sha256_hex>
    SHA-256 hash of the raw request body, hex-encoded

Example Canonical String

1766494092286
550e8400-e29b-41d4-a716-446655440000
/webhooks/mutation
a9c2e4b7f4c8...

Verify a callback

To verify a callback request:

Read the raw body

Read the raw request body before parsing JSON.

Hash and reconstruct

Compute the body’s hex-encoded SHA-256 hash and reconstruct the canonical string exactly.

Calculate the expected signature

Use the regional signing secret to create an HMAC-SHA256, then Base64-encode it.

Compare securely

Remove v2= from the received signature, compare values in constant time, and reject failures.

Replay Protection

The timestamp and nonce are included to mitigate replay attacks. We recommend that you:

  • Reject callbacks with timestamps older than 15 minutes
  • Track recently seen nonces and reject duplicates (optional but recommended)

Failure Behavior

If your callback endpoint responds with a non-2xx status code:

  • The callback will be retried automatically
  • Retries use exponential backoff
  • Callback delivery failures do not affect mutation execution

Callbacks are a delivery mechanism only. A mutation may succeed even if all callback attempts ultimately fail.

Examples

The examples below mirror the Mutation Engine’s signing logic, including:

  • Canonical string layout
  • SHA-256 body hashing
  • Base64-encoded HMAC
  • Required trailing newline
import crypto from 'crypto'

export function verifyCallbackSignature(
  req: {
    headers: Record<string, string | undefined>
    rawBody: string
    url: string
  },
  signingSecret: string
): boolean {
  const timestamp = req.headers['x-mutationengine-timestamp']
  const nonce = req.headers['x-mutationengine-nonce']
  const signatureHeader = req.headers['x-mutationengine-signature']

  if (!timestamp || !nonce || !signatureHeader) return false
  if (!signatureHeader.startsWith('v2=')) return false

  const receivedSignature = signatureHeader.slice(3)

  const url = new URL(req.url)
  const pathAndQuery = `${url.pathname}${url.search}`

  const bodyHashHex = crypto
    .createHash('sha256')
    .update(req.rawBody, 'utf8')
    .digest('hex')

  const canonical = [
    timestamp,
    nonce,
    pathAndQuery,
    bodyHashHex,
    ''
  ].join('\n')

  const expectedSignature = crypto
    .createHmac('sha256', signingSecret)
    .update(canonical, 'utf8')
    .digest('base64')

  return crypto.timingSafeEqual(
    Buffer.from(expectedSignature),
    Buffer.from(receivedSignature)
  )
}

Use a constant-time comparison in every implementation: timingSafeEqual in Node.js, hash_equals in PHP, and hmac.compare_digest in Python. Always read raw request bytes and combine verification with timestamp freshness checks and optional nonce tracking.

Keep in touch with the latest

Sign up for our monthly deep dives - straight to your inbox.