Credentials
On Meridian hosting, your next deploy injectsMERIDIAN_WEBHOOK_SECRET and MERIDIAN_API_KEY automatically, along with MERIDIAN_APP_ID and MERIDIAN_API_URL. The signing secret verifies forwards and the host challenge. The API key authenticates SDK calls such as event tracking.
For local development or hosting elsewhere, declare these credentials yourself. A manually declared MERIDIAN_WEBHOOK_SECRET or MERIDIAN_API_KEY also takes precedence on Meridian hosting. See Credentials and environment for the managed values, override rules, and staging API configuration.
The receiver route
One route handles both the verification handshake and real events. Try the challenge first, then fall through:app/routes/webhooks.shopify.tsx
/webhooks/shopify. Your dashboard’s webhook configuration decides which topics reach it. See Pub/Sub.
The host verification challenge
An app hosted outside Meridian (Heroku, Fly, your own server) receives forwarded webhooks at an external domain. Declare itshttps:// origin on the app’s Settings > Domains page, under External domains, then click Verify. Meridian sends a signed POST to your forward path on that origin with the body { type: "meridian/verify", nonce, app_id }, expecting an answer only the holder of your signing secret can compute. A domain that answers is verified: your app runs there and its SDK holds the secret. It does not prove who owns the domain.
Then pick the domain in the Forward host select on the Pub/Sub > Webhooks page. The select lists your Meridian deployment when the app is deployed, and every verified external domain. Pending and failed domains are listed but can’t be picked, so the forward host you choose takes effect from the next event. See External domains.
meridianChallengeResponse computes the answer. A non-null result is the JSON to respond with, at HTTP 200. null means this is not a challenge, or the signature did not verify. Fall through to the event path.
Replay protection
verifyMeridianWebhook is the one to use. Forwards carry two headers:
The timestamp has to be within 300 seconds of now, in either direction, and a signature has to match, so a captured request stops verifying once the window passes. Override it with
toleranceSeconds.
A request arriving without those headers falls back to the legacy body-only check, which has no replay protection. Pass requireTimestamp: true to refuse the fallback instead.
verifyForwardedWebhook(rawBody, signatureHeader, secret) is that legacy check on its own. Prefer verifyMeridianWebhook.
Rotating the signing secret
The signing secret comes from your app in the Meridian dashboard. Meridian hosting injects it asMERIDIAN_WEBHOOK_SECRET; elsewhere, set that variable yourself.
When you rotate it, the v2 header carries a signature for both secrets for 24 hours, so a receiver still holding the old value keeps verifying while you roll out the new one. Two details matter during that window:
- The legacy body-only check carries one signature, so it switches to the new secret immediately. An app still on
verifyForwardedWebhookgets no grace period. - The challenge answer is accepted from either secret for the same 24 hours.
MERIDIAN_WEBHOOK_SECRET yourself, update its value before redeploying.
What the SDK registers for you
You never declare webhook topics.afterAuth reconciles a shop’s Pub/Sub subscriptions from the set Meridian resolves (the managed billing topics plus whatever you routed), and unions the managed ones in even when the configuration is unreachable, so an outage cannot drop billing events.
Four topics are managed by the platform and always registered:
Apps on the same Meridian environment publish to that environment’s Pub/Sub topic, so each subscription is named with your app’s ID for routing. The SDK asks the API which topic to target, so you never configure it.
registerMeridianWebhooks registers the four managed topics alone and, because it takes no API key, defaults to the production topic (pass delivery to target another environment); registerWebhooks on the app object reconciles the full set, including your own topics, against the served target.
Signature constants
If you build the verification yourself, the header names and the default window are exported:MERIDIAN_SIGNATURE_HEADER, MERIDIAN_SIGNATURE_V2_HEADER, MERIDIAN_TIMESTAMP_HEADER and MERIDIAN_SIGNATURE_TOLERANCE_SECONDS.