Agent skill
Omnisend Webhooks Skill
Receive and authenticate Omnisend webhooks sent by the automation "send webhook" action. Use when setting up an Omnisend webhook endpoint, checking a shared secret header such as X-Webhook-Secret, debugging 401s, or handling automation payloads built from [[contact.*]] and [[event.raw.*]] tags (for example event "subscribed").
Install this skill
npx skills add hookdeck/webhook-skills --skill omnisend-webhooks
Omnisend (ecommerce email and SMS) sends webhooks from the send webhook action in an automation workflow (sendWebhook block, paid plans only, not the free plan). Each time a contact reaches that block, Omnisend POSTs your JSON body template, with [[contact.*]] and [[event.raw.*]] tags resolved, to your HTTPS callbackUrl.
Requests are not signed. Omnisend lets you add up to 15 custom headers to the action, and the docs recommend a shared secret header such as X-Webhook-Secret. Your endpoint authenticates the request by comparing that header with the value you stored.
When to Use This Skill
- How do I receive Omnisend webhooks from an automation?
- How do I verify an Omnisend webhook? (No signature: check a shared secret header)
- How do I check
X-Webhook-Secreton an Omnisend webhook request? - How do I handle an Omnisend payload with
"event": "subscribed"? - Why does my Omnisend webhook endpoint return 401?
Verification (core)
const crypto = require('crypto');
// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
function safeEqual(a, b) {
const ha = crypto.createHash('sha256').update(String(a)).digest();
const hb = crypto.createHash('sha256').update(String(b)).digest();
return crypto.timingSafeEqual(ha, hb);
}
function verifyOmnisendRequest(headers, {
headerName = process.env.OMNISEND_WEBHOOK_HEADER || 'X-Webhook-Secret',
secret = process.env.OMNISEND_WEBHOOK_SECRET,
} = {}) {
if (!secret) return false; // never accept when unconfigured
const received = headers[headerName.toLowerCase()]; // Node lowercases header names
return typeof received === 'string' && safeEqual(received, secret); // whole value, exact
}
- The header name is whatever you typed in the webhook action's headers; the examples read it from
OMNISEND_WEBHOOK_HEADER(defaultX-Webhook-Secret). Look it up case-insensitively. - Compare the whole header value with
OMNISEND_WEBHOOK_SECRET. Do not strip or parse aBearerprefix: if you typedBearer abcas the header value, the secret isBearer abc. - Return
401on a missing header or mismatch,400for a body that isn't a JSON object,200once handled. - Read the body raw (
express.raw({ type: '*/*' })) and authenticate before parsing it. - A static shared credential authenticates the sender, not the body, and gives no replay protection (no signature, no timestamp). Use HTTPS (Omnisend requires it) and rotate the credential if it leaks.
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Event Types
Omnisend does not define a fixed webhook event schema. The body is your JSON template, so the event name is whatever you put in it. The official guide's example sets "event": "subscribed" by hand for an automation triggered by the subscribed to marketing event.
event value | Where it comes from |
|---|---|
subscribed | Value hard-coded in the guide's example body (trigger: subscribed to marketing) |
placed order | Illustrative: Omnisend's docs use this as a trigger name, so it arrives only if your template sets it |
| anything else | Whatever your template sets; use [[event.raw.<property>]] for trigger event data |
The examples dispatch on event and log unknown values, so add a case per automation.
Environment Variables
OMNISEND_WEBHOOK_HEADER=X-Webhook-Secret # header name you set in the webhook action (default)
OMNISEND_WEBHOOK_SECRET=your-shared-secret # exact, whole header value
Local Development
Omnisend can't reach localhost, and callbackUrl must be HTTPS on a public domain. Tunnel with the Hookdeck CLI (no account required):
npx hookdeck-cli listen 3000 omnisend --path /webhooks/omnisend
Use the printed URL as the callbackUrl of the webhook action.
Resources
- references/overview.md - What Omnisend automation webhooks are, payload shape
- references/setup.md - Add the send webhook action and the secret header
- references/verification.md - Header check details, gotchas, debugging 401s
- examples/ - Runnable Express, Next.js and FastAPI handlers with tests