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.
Enhanced Verification with Replay Protection (Recommended)
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
reasonfield 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
| Header | Description |
|---|---|
X-Webhook-ID | Unique delivery identifier — use for idempotency checks |
X-Webhook-Event | Event type (e.g., order.created) |
X-Webhook-Signature | HMAC-SHA256 signature |
X-Webhook-Timestamp | Unix timestamp of when the signature was generated |
Signature Algorithm
Whatalo generates the signature as follows:
- Concatenate:
${timestamp}.${rawBody} - Compute HMAC-SHA256 using your client secret as the key
- Hex-encode the result
- 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.timingSafeEqualor the SDK helper. String equality (===) is vulnerable to timing attacks. - Enforce the replay window — Reject requests with a
X-Webhook-Timestampolder than 5 minutes. - Validate
X-Webhook-Event— Only process event types your plugin declared in the manifest. - Store
X-Webhook-IDfor 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-storeThe 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.