Skip to main content

Webhook Verification

Every webhook LaunchMyStore delivers is signed with HMAC-SHA256 using a signing secret, and the signature is sent in the X-LMS-Hmac-SHA256 header. Your endpoint MUST verify this signature before trusting the payload — otherwise anyone who knows your callback URL can post forged events to it. Which secret signs the delivery depends on how the webhook was registered:
Use the secret that matches how the webhook was created:
  • API-registered app webhooks → the subscription’s own secret, returned once in the POST /api/v1/webhooks.json create response. Store it then — it is never shown again when you list or read subscriptions.
  • Manifest-declared app webhooks and GDPR compliance webhooks → your app’s clientSecret.
  • Store-level webhooks → the webhook’s own secret, re-viewable any time in Store Admin → Tools → Webhooks (Reveal / Copy).
Treat whichever applies like a password — never embed it in client-side code.
This page covers:
  • The exact signing algorithm and which body is signed
  • All headers sent with the request
  • Verification examples in Node.js, Python, Ruby, PHP, and Go using timing-safe comparison
  • Retry semantics — when LaunchMyStore re-attempts a failed delivery

Signature spec

The signature is computed exactly as:
Where:
  • webhook_secret is the signing secret for this webhook — see the table above for which one applies (the per-subscription secret from POST /api/v1/webhooks.json, your app’s clientSecret for manifest-declared and GDPR webhooks, or the per-webhook secret shown in the merchant admin for store-level webhooks). Treat it like a password — never embed it in client-side code. (The WEBHOOK_SECRET variable in the examples below holds whichever of these applies to your webhook.)
  • raw_request_body is the exact bytes of the HTTP request body as it appears on the wire. Do not parse-and-reserialize the JSON before computing — a single whitespace difference fails the check.
  • base64 is standard (not URL-safe) base64 with +// and padding.
The result is sent in the X-LMS-Hmac-SHA256 header (see below).

Headers

Every webhook request includes these headers:

Verification examples

All examples below:
  1. Read the raw body before any JSON parsing happens.
  2. Compute the same HMAC.
  3. Compare to the header using timing-safe comparison (not ==).

Common verification pitfalls

The HMAC is computed over the raw bytes of the body. If your framework parses JSON and your handler re-serializes it, whitespace, key order, and floating-point formatting differences will fail the check. Always grab the raw body first.
Plain string comparison short-circuits on the first mismatched byte, leaking the prefix of a valid signature to an attacker who can measure response time. Use crypto.timingSafeEqual (Node), hmac.compare_digest (Python), hash_equals (PHP), secure_compare (Ruby), or hmac.Equal (Go).
Node’s crypto.timingSafeEqual throws if the inputs differ in length. Guard with an early length check and return false — don’t let the exception propagate as a 500.
The signature transmitted in the header is base64. Compare the base64 strings directly — don’t decode them to bytes first (works, but is two more lines of error-prone code).
If X-LMS-Hmac-SHA256 is not present, reject with 401. Never assume an unsigned request is legitimate.

Idempotency

LaunchMyStore retries on non-2xx responses (see below). Implement idempotency keyed on X-LMS-Webhook-Id to avoid double-processing when a retry races your slow first response:
For production, store the delivery id in a database or Redis with a 24-hour TTL — long enough to outlast LaunchMyStore’s retry window.

Retry semantics

If your endpoint returns a non-2xx response (or fails to respond within the 10-second timeout), LaunchMyStore retries automatically. Total: up to 3 retries after the initial attempt = 4 send attempts spread across roughly 16 minutes. Each attempt’s number is exposed in the X-LMS-Delivery-Attempt header (1, 2, 3, 4).

Status codes and retry behaviour

  • 2xx: Delivery marked SUCCESS. No further attempts.
  • 429 / 5xx: Delivery retried up to MAX_RETRIES (3) using the schedule above. After exhaustion: FAILED.
  • 4xx (except 429): Treated as a permanent client error. No retry. The webhook is marked FAILED immediately. If your endpoint returns 400 for a transient parse error, you will silently miss that event — return 5xx for transient errors so the retries run.
  • Network error / timeout: Treated the same as a transient failure and retried.

Timeout

Each delivery attempt has a 10-second HTTP timeout on LaunchMyStore’s side. If your handler doesn’t respond in 10 seconds the attempt is recorded as a network error and a retry is scheduled. Keep your handler fast — ack quickly, do work asynchronously:

IP allowlist

LaunchMyStore does not publish a stable allowlist of source IPs for webhook delivery — the delivery worker runs in a cluster whose egress can rotate. Rely on the HMAC signature, not IP filtering, to authenticate webhooks. If your network forces source-IP allowlists, contact LaunchMyStore support to discuss a fixed-egress arrangement (typically only granted on enterprise tier).

Re-delivering a past event

There is no synthetic “send a sample payload” test endpoint. What you can do is re-send a real delivery that already happened — useful when your endpoint was down or returned an error and you want to replay the exact same payload (and signature) again. This is a merchant/developer-portal action, authenticated with the merchant or partner session (not an OAuth app access token):
{deliveryId} is the id of an existing delivery-log entry (the same value sent in the X-LMS-Webhook-Id header). The retry re-POSTs the original payload to your endpoint with the live wire format — same signing key, same headers, same HMAC computation — so it’s a faithful way to confirm your verifier handles a real delivery. The endpoint returns the updated delivery-log record in the standard envelope:

See also

Webhooks Overview

Subscribe to topics, register endpoints, manage subscriptions.

Topics

The supported topics and their payload shapes.

Authentication

OAuth flow and where the client secret comes from.

API Rate Limits

Limits on outbound API calls — webhooks are zero-cost on your side.