Gareth Wilson Gareth Wilson

Guide to Omnisend Webhooks: Features and Best Practices

Published


Omnisend is an email and SMS marketing platform for ecommerce stores. Its automation workflows react to what shoppers do, such as subscribing, placing an order or abandoning a cart, and a webhook step in a workflow lets that moment reach another system: a CRM, a messaging service, an order management tool or your own API.

This guide covers how Omnisend's webhook action works, what it sends, how to authenticate it with a secret header, the limits on URLs and headers, and the practices that keep a receiver reliable in production.

What are Omnisend webhooks?

Omnisend sends webhooks from automation workflows, not from a store-wide subscription. You add a webhook block (the sendWebhook action in the API) at a point in a workflow, and each time a contact reaches that block, Omnisend sends an HTTP POST to your URL. Webhooks in automations are only available on paid plans.

You write the body yourself, as a JSON template with personalisation tags such as [[contact.email]] and [[event.raw.<property>]], which Omnisend fills in for each contact. That means Omnisend defines no webhook event types. If your endpoint needs to know which workflow sent a request, put that in the body.

Omnisend does not sign requests. Its documentation lists two ways to verify that a request came from Omnisend: a shared secret in a custom header, such as X-Webhook-Secret, that your server validates, and allowlisting Omnisend's seven static IP addresses.

Omnisend webhook features

FeatureDetails
Where they come fromA webhook block (sendWebhook action) in an automation workflow
PlansPaid plans only
Method and formatPOST, Content-Type: application/json
BodyYour JSON template with personalisation tags, max 64 KB, must be valid JSON
URLHTTPS, a fully qualified domain on port 443, valid TLS certificate, max 2,000 characters. No IP literals or internal addresses
Custom headersUp to 15. Key max 256 characters, value max 10,000. Keys are case-insensitive and must be unique
Reserved headersHost, Content-Length, Transfer-Encoding, Connection, Keep-Alive, Upgrade, TE, Trailer, Proxy-Authorization, Proxy-Authenticate, Expect, User-Agent and Content-Type cannot be set
AuthenticationA shared secret header you add. No signature
Source IPs34.170.162.11, 34.56.62.59, 34.56.108.215, 34.133.59.36, 34.58.66.146, 35.184.130.5, 35.232.14.89
Timeouts and retriesNot documented

What a request looks like

Omnisend's API guide uses this body template for an automation triggered when a contact subscribes to marketing:

{
  "contactEmail": "[[contact.email]]",
  "contactName": "[[contact.first_name]]",
  "event": "subscribed"
}

event here is a value the template author typed, not one Omnisend adds. Contact tags include [[contact.email]], [[contact.first_name]], [[contact.last_name]] and [[contact.phone_number]], and [[event.raw.<property>]] reads a property of the event that triggered the workflow, such as an order total. The rendered body is sent with Content-Type: application/json and your custom headers.

Inspect Omnisend webhooks as they arrive. Point a webhook block at a Hookdeck Inspect URL to see the rendered body and headers Omnisend sends, with no account or setup required.

Setting up Omnisend webhooks

Generate a long random secret first, for example with openssl rand -hex 32, and decide which header it will travel in.

  1. In Omnisend, create or edit an automation workflow.
  2. Drag a Webhook block to the point in the workflow where the request should be sent.
  3. Enter your HTTPS endpoint as the URL.
  4. Add your secret header, such as X-Webhook-Secret with your secret as the value. Whatever you type is sent as-is, so include Bearer if you use Authorization and want a bearer token.
  5. Write the JSON body, including a field that names the workflow.
  6. Test the webhook and confirm your endpoint returns 200 OK, then enable the automation.

You can also create the workflow through the Automations API with an API key that has the automations.write scope. Automations are created disabled, and an enabled automation has to be disabled before you replace its blocks.

To put Hookdeck in front of your service, create a Hookdeck source with the Omnisend type and use its URL in step 3. The source asks for a Header Name and a Header Value: enter the same name and the whole value you typed in Omnisend. Header names match case-insensitively, but the value must match exactly, prefix included. The source accepts POST, which is the only method Omnisend uses.

For local development, hookdeck listen 3000 omnisend --path /webhooks/omnisend gives you a public HTTPS URL on port 443 that forwards to your machine, which meets Omnisend's URL rules.

Securing Omnisend webhooks

Read the header you chose and compare its whole value with your stored secret in constant time. Reject the request if the header is missing or the secret isn't configured.

const crypto = require('crypto');
const express = require('express');

const HEADER_NAME = process.env.OMNISEND_WEBHOOK_HEADER || 'x-webhook-secret';
const SECRET = process.env.OMNISEND_WEBHOOK_SECRET;

function safeEqual(received, expected) {
  const a = Buffer.from(received, 'utf8');
  const b = Buffer.from(expected, 'utf8');
  // timingSafeEqual throws on unequal lengths, so check first.
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

const app = express();

app.post('/webhooks/omnisend', express.raw({ type: '*/*' }), (req, res) => {
  const received = req.get(HEADER_NAME); // case-insensitive lookup
  if (!SECRET || typeof received !== 'string' || !safeEqual(received, SECRET)) {
    return res.sendStatus(401);
  }

  let payload;
  try {
    payload = JSON.parse(req.body.toString('utf8'));
  } catch {
    return res.sendStatus(400);
  }

  // Route on the field your template sets, e.g. payload.event.
  res.sendStatus(200);
});

The same check in Python:

import hmac
import os

HEADER_NAME = os.environ.get("OMNISEND_WEBHOOK_HEADER", "X-Webhook-Secret")
SECRET = os.environ.get("OMNISEND_WEBHOOK_SECRET", "")


def verify_omnisend_request(headers) -> bool:
    if not SECRET:
        return False  # never accept when unconfigured
    received = headers.get(HEADER_NAME)  # Starlette and Flask headers are case-insensitive
    if not received:
        return False
    # Compare bytes: compare_digest raises TypeError on a non-ASCII str.
    return hmac.compare_digest(received.encode("utf-8"), SECRET.encode("utf-8"))

A static header proves the sender knows the secret. It does not cover the body and carries no timestamp, so a modified or replayed request still passes. Omnisend already requires HTTPS. Use a long random value, rotate it if it might have leaked, and if your firewall allows it, add Omnisend's IP list as a second layer.

Make Omnisend webhooks production-ready. Hookdeck Event Gateway checks the secret header, stores every request, and retries delivery to your service on a schedule you control.

Omnisend webhook limitations and pain points

A secret header is all you get

The Problem: Nothing in an Omnisend request proves the body is unchanged or that the request is new.

Why It Happens: Omnisend's verification options are a shared secret header and an IP allowlist. Neither is computed over the body.

Workarounds:

  • Use a long random secret, and allowlist Omnisend's seven IPs at your firewall.
  • Make handlers idempotent so a replayed request does no harm.

How Hookdeck Can Help: Hookdeck's Omnisend source compares the header you name with the exact value you enter. Hookdeck still answers with a 200, but a request with a missing or wrong value is recorded as failed verification and never reaches your destination.

Retry behaviour is undocumented

The Problem: If your endpoint is down when a contact reaches the webhook block, you can't tell from the documentation whether Omnisend will try again.

Why It Happens: Omnisend documents how to configure and verify webhooks, but not their timeout, retry policy or redelivery.

Workarounds:

  • Keep the endpoint highly available and return 200 quickly.
  • For workflows that must not be missed, reconcile against Omnisend's API on a schedule.

How Hookdeck Can Help: Hookdeck accepts and stores each request as it arrives, then delivers it to your service with retries you configure. Issues alert you when deliveries fail, and you can replay any request after a fix.

The body is a template, not a schema

The Problem: A missing contact property or an edit to the template breaks your parser, and nothing in the request says which workflow it came from.

Why It Happens: The body is whatever JSON the template author wrote, filled in per contact, and Omnisend adds no event field of its own. Not every contact has every property.

Workarounds:

  • Agree a body convention: an event or workflow key, the contact email, and the trigger properties you need.
  • Treat every templated field as possibly empty.

How Hookdeck Can Help: Hookdeck filters can route requests to different destinations by a body field, so each workflow's webhooks reach the service that handles them.

Strict URL rules complicate testing

The Problem: Omnisend rejects a webhook URL that points at localhost, an IP address or a non-standard port.

Why It Happens: The URL must be HTTPS on a fully qualified domain on port 443 with a valid certificate, and internal addresses are blocked.

Workarounds:

  • Use a tunnel or a staging domain with a real certificate.

How Hookdeck Can Help: The Hookdeck CLI gives you a public HTTPS URL on port 443 that forwards to any local port.

Best practices

Name the workflow in the body

Put a fixed event or workflow value in every template, as Omnisend's own example does with "event": "subscribed". It is the only reliable way to route requests from several workflows on one endpoint.

Deduplicate

Omnisend adds no request ID. Include something unique in the template, such as the contact email plus an order ID from [[event.raw.<property>]], and store it to skip repeats. See our guide to webhook idempotency.

Keep secrets out of the body

Use a header for the secret. Bodies are visible to everyone who can edit the workflow and often end up in logs.

Acknowledge fast and process asynchronously

Return 200 once the header checks out, and do the work from a queue. See why to process webhooks asynchronously.

Conclusion

Omnisend sends webhooks from automation workflows, as JSON you template yourself, with no signature and no documented retry policy. Add a long random secret header and compare its whole value in constant time, allowlist Omnisend's IPs if you can, name the workflow in every body, and build in deduplication.

Hookdeck Event Gateway checks the secret header and holds each request until your service has processed it, which covers the retry behaviour Omnisend doesn't document.

Get started with Hookdeck for free and handle Omnisend webhooks reliably in minutes.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.