Webhooks

Verification & Security

Verify Whatalo webhook signatures using HMAC-SHA256 to prevent forged requests and replay attacks.

Every webhook request from Whatalo includes an HMAC-SHA256 signature. Always verify this signature before processing any event payload — it guarantees the request originated from Whatalo and has not been tampered with.

SDK Verification (Simple)

For most plugins, the SDK helper is the fastest path:

import { verifyWebhook } from "@whatalo/plugin-sdk/webhooks";

// rawBody must be the raw, unparsed request body string
const isValid = verifyWebhook({
  payload: rawBody,
  signature: req.headers["x-webhook-signature"] as string,
  secret: process.env.WHATALO_CLIENT_SECRET!,
});

if (!isValid) {
  return res.status(401).json({ error: "Invalid signature" });
}

This performs HMAC-SHA256 verification using your client secret as the key. It is the baseline verification method.

The template generated by whatalo init includes a more robust verifier (src/webhooks/verify.ts) that also guards against replay attacks:

// src/webhooks/verify.ts (scaffolded by whatalo init)

/**
 * Verifies the HMAC-SHA256 signature of an incoming Whatalo webhook request.
 * Includes replay protection: signatures older than 300 seconds are rejected.
 */
function verifyWhataloWebhook(
  headers: Record<string, string | string[] | undefined>,
  rawBody: string,
  secret: string
): { valid: boolean; reason?: string }

This enhanced version:

  • Signs ${timestamp}.${rawBody} — including the timestamp in the signed content
  • Enforces a 300-second replay tolerance window — requests older than 5 minutes are rejected even if the signature is valid
  • Reads three headers: X-Webhook-Id, X-Webhook-Timestamp, X-Webhook-Signature
  • Returns a structured result with an optional reason field for debugging

Usage

import { verifyWhataloWebhook } from "@/webhooks/verify";

const result = verifyWhataloWebhook(
  req.headers as Record<string, string | undefined>,
  rawBody,
  process.env.WHATALO_CLIENT_SECRET!
);

if (!result.valid) {
  console.warn("Webhook verification failed:", result.reason);
  return res.status(401).json({ error: "Invalid signature" });
}

Signature Headers

HeaderDescription
X-Webhook-IDUnique delivery identifier — use for idempotency checks
X-Webhook-EventEvent type (e.g., order.created)
X-Webhook-SignatureHMAC-SHA256 signature
X-Webhook-TimestampUnix timestamp of when the signature was generated

Signature Algorithm

Whatalo generates the signature as follows:

  1. Concatenate: ${timestamp}.${rawBody}
  2. Compute HMAC-SHA256 using your client secret as the key
  3. Hex-encode the result
  4. Send as X-Webhook-Signature

You can replicate this in any language:

import crypto from "node:crypto";

function computeSignature(rawBody: string, timestamp: string, secret: string): string {
  const signedContent = `${timestamp}.${rawBody}`;
  return crypto
    .createHmac("sha256", secret)
    .update(signedContent)
    .digest("hex");
}

Security Checklist

Skipping any of these checks opens your plugin to forged or replayed requests.

  • Use the raw body — Parse JSON only after verification. Any modification to the body before computing the HMAC will cause verification to fail.
  • Compare signatures in constant time — Use crypto.timingSafeEqual or the SDK helper. String equality (===) is vulnerable to timing attacks.
  • Enforce the replay window — Reject requests with a X-Webhook-Timestamp older than 5 minutes.
  • Validate X-Webhook-Event — Only process event types your plugin declared in the manifest.
  • Store X-Webhook-ID for idempotency — Webhook deliveries can be retried. Process each delivery ID only once.

Testing Webhooks Locally

Use the CLI to trigger test events against your local development server:

# Trigger an order.created event against your dev store
whatalo webhook trigger ORDER_CREATED --store my-dev-store

The CLI signs the request with your development client secret, so your verification logic runs exactly as it would in production. Only works with development stores.

See the CLI Reference — webhook for the full list of supported test events.

On this page