Receive Signed Store MCP Webhooks
Webhook destinations send JSON signed with HMAC-SHA256. Your receiver must verify the raw bytes and make processing idempotent.
Prerequisites
Choose a plan that includes webhook destinations, a public HTTPS endpoint that does not redirect, and a signing secret stored securely at the receiver. Do not put the secret in prompts, source control or logs. The receiver implementation is your responsibility.
Steps
- Create a webhook destination in Notifications using your endpoint. Select supported subscriptions and store scope. Copy the generated signing secret from the one-time browser response into your receiver’s secret manager before leaving that page.
- At the receiver, capture the exact body bytes before JSON parsing. Read X-Store-MCP-Timestamp and X-Store-MCP-Signature.
- Verify HMAC-SHA256(secret, timestamp + "." + rawBody) against the v1= hexadecimal signature with constant-time comparison. Apply a timestamp tolerance; the example uses five minutes.
- After verification, parse the body and durably deduplicate the verified event id with the business operation. Return a successful response for an already processed delivery. Run a marked test and verify it at the receiver.
Safe example
The example uses synthetic names. Select your own authorized store and verify every identifier before a write.
// Node.js: verify the raw body before parsing JSON. Never log the secret.
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verify(rawBody, timestamp, signature, secret, nowSeconds) {
if (!secret || !/^\d+$/.test(timestamp) || !/^v1=[a-f0-9]{64}$/.test(signature)) return false;
if (Math.abs(nowSeconds - Number(timestamp)) > 300) return false;
const expected = createHmac('sha256', secret).update(timestamp + '.').update(rawBody).digest();
const supplied = Buffer.from(signature.slice(3), 'hex');
return supplied.length === expected.length && timingSafeEqual(supplied, expected);
}
// Pass the exact request body bytes, X-Store-MCP-Timestamp,
// X-Store-MCP-Signature, process.env.STORE_MCP_WEBHOOK_SECRET,
// and Math.floor(Date.now() / 1000). Then durably deduplicate the verified event id.You’re done when…
The valid test passes signature verification; a changed byte, wrong secret or stale timestamp fails. X-Store-MCP-Delivery identifies the event and X-Store-MCP-Event names its type. Compare those headers with the verified body rather than trusting unsigned headers alone.
Limitations
The signature does not itself prevent replay. Timestamp freshness and durable deduplication are receiver controls. A process-memory set is insufficient across restarts. There is no exactly-once delivery guarantee.
Synthetic test payload
No buyer data or raw provider payload is included. Approval notices may also include allowlisted approval context and an owner sign-in URL.
{
"id": "evt_example",
"type": "order.created",
"workspaceId": "workspace_example",
"storeId": "store_example",
"platform": "shopify",
"resourceId": "test",
"occurredAt": "2026-09-21T00:00:00.000Z",
"test": true
}Delivery and coverage
Timeouts, 429 and server failures can be retried through the queue. Redirects are not followed. A receiver may have processed a message before a response was lost, so repeated delivery must be harmless. There is no historical order backfill or guarantee that every event reaches a destination.
A successful test has test: true and does not establish live order ingestion. Shopify and Etsy ingestion has fixture evidence and separate configuration/availability gates; eBay, Square and WooCommerce currently produce no Store MCP order events.