How to send webhooks from automation workflows

Intro

Use the sendWebhook action block in automation workflows to send real-time HTTP POST requests to external systems when contacts reach a specific point in a workflow. This enables integrations with CRMs, messaging platforms (Slack, WhatsApp), order management systems, or any custom API endpoint — without building a native integration.

See API reference: Automations

Prerequisites

  • An API key with automations.write scope (or an OAuth token with the same scope)
  • A paid Omnisend plan — webhooks in automations are not available on the free plan
  • An HTTPS endpoint to receive the webhook (for testing, webhook.site provides a temporary URL)

Step 1: Create an automation with a webhook action

Create a disabled automation workflow that triggers on an event and sends an HTTP POST to your endpoint. The example below triggers when a contact subscribes to marketing, waits 5 minutes, then sends contact data to an external URL.

See endpoint: Create automation workflow

curl -X POST 'https://api.omnisend.com/api/automations' \
  -H 'Authorization: Omnisend-API-Key YOUR-API-KEY' \
  -H 'Omnisend-Version: 2026-preview' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "New Subscriber Webhook",
    "trigger": {
      "condition": {
        "event": "subscribed to marketing"
      }
    },
    "blocks": [
      {
        "temporaryID": "wait-5m",
        "type": "delay",
        "delay": {
          "mode": "duration",
          "duration": {
            "amount": 5,
            "units": "m"
          }
        }
      },
      {
        "temporaryID": "webhook-notify",
        "type": "action",
        "action": {
          "type": "sendWebhook",
          "sendWebhook": {
            "callbackUrl": "https://example.com/webhooks/omnisend",
            "headers": [
              {"key": "X-Webhook-Secret", "value": "my-shared-secret"}
            ],
            "body": "{\"contactEmail\": \"[[contact.email]]\", \"contactName\": \"[[contact.first_name]]\", \"event\": \"subscribed\"}"
          }
        }
      }
    ]
  }'

The response returns the automation in disabled state with an id and the resolved blocks array (each block now has a server-assigned id replacing temporaryID).

Note: The callbackUrl must be HTTPS. IP literals, non-443 ports, non-FQDN hosts, and internal/private IP addresses are rejected. The body must be valid JSON and supports personalization tags like [[contact.email]], [[contact.first_name]], and [[event.raw.<property>]]. The trigger.condition.event value must be a valid event for your brand — some events (e.g., "placed order") require an origin field specifying the source platform.

Step 2: Verify the webhook configuration

Retrieve the automation to confirm the webhook block was created correctly.

See endpoint: Get automation workflow

curl -X GET 'https://api.omnisend.com/api/automations/AUTOMATION_ID' \
  -H 'Authorization: Omnisend-API-Key YOUR-API-KEY' \
  -H 'Omnisend-Version: 2026-preview'

In the response, look for the blocks array. The webhook block will contain:

{
  "type": "action",
  "action": {
    "type": "sendWebhook",
    "sendWebhook": {
      "callbackUrl": "https://example.com/webhooks/omnisend",
      "headers": [
        {"key": "X-Webhook-Secret", "value": "my-shared-secret"}
      ],
      "body": "{\"contactEmail\": \"[[contact.email]]\", ...}"
    }
  }
}

Step 3: Update the webhook (optional)

To change the webhook URL, body, or headers on an existing automation, use PATCH. Only provided fields are updated.

See endpoint: Patch automation workflow

curl -X PATCH 'https://api.omnisend.com/api/automations/AUTOMATION_ID' \
  -H 'Authorization: Omnisend-API-Key YOUR-API-KEY' \
  -H 'Omnisend-Version: 2026-preview' \
  -H 'Content-Type: application/json' \
  -d '{
    "blocks": [
      {
        "id": "WEBHOOK_BLOCK_ID",
        "type": "action",
        "action": {
          "type": "sendWebhook",
          "sendWebhook": {
            "callbackUrl": "https://example.com/webhooks/omnisend/v2",
            "body": "{\"contactEmail\": \"[[contact.email]]\", \"contactPhone\": \"[[contact.phone_number]]\", \"event\": \"subscribed\"}"
          }
        }
      }
    ]
  }'

Note: The automation must be disabled before replacing blocks. If it is currently enabled, disable it first via POST /automations/{id}/disable, replace blocks, then re-enable.

Webhook configuration reference

FieldRequiredDescription
callbackUrlYesHTTPS URL that receives the POST request (max 2,000 characters)
bodyYesJSON body template with optional personalization tags (max 64 KB)
headersNoUp to 15 custom HTTP headers (key max 256 chars, value max 10,000 chars)

URL restrictions

  • Must use https:// scheme
  • Must be a fully-qualified domain name (no IP literals)
  • Only port 443 is allowed (or omit port entirely)
  • Internal/private IP addresses are blocked (DNS is resolved externally)
  • TLS certificate must be valid

Forbidden header keys

The following headers cannot be set as custom headers (they are managed by the system): Host, Content-Length, Transfer-Encoding, Connection, Keep-Alive, Upgrade, TE, Trailer, Proxy-Authorization, Proxy-Authenticate, Expect, User-Agent, Content-Type.

Personalization tags

The webhook body supports the same personalization variables available in automation messages:

  • [[contact.email]], [[contact.first_name]], [[contact.last_name]], [[contact.phone_number]]
  • [[event.raw.<property>]] — access properties from the triggering event

For a full list of available personalization tags, see Use personalization in Omnisend.

Receiving and verifying webhooks

When the automation fires, Omnisend sends an HTTP POST to your callbackUrl with:

  • Content-Type: application/json
  • Your custom headers (as configured)
  • The JSON body with personalization tags resolved to actual contact/event values

To verify the request comes from Omnisend:

  1. Shared secret header — include a secret value in a custom header (e.g., X-Webhook-Secret) and validate it on your server.
  2. IP whitelisting — Omnisend sends webhook requests from these static IPs: 34.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.

Testing with webhook.site

For quick testing without setting up your own server:

  1. Go to webhook.site and copy your unique URL
  2. Use that URL as the callbackUrl when creating the automation
  3. Enable the automation and trigger the event — the request will appear on webhook.site

Troubleshooting

  • 400 on create/patch with callbackUrl error: The URL is not HTTPS, uses an IP literal, or resolves to an internal address. Use a public HTTPS domain.
  • 400 with body validation error: The body field must contain valid JSON. Validate your JSON before submitting.
  • 409 Conflict on PATCH: The automation is currently enabled. Disable it first via POST /automations/{id}/disable, make changes, then re-enable.
  • Webhook not reaching your endpoint: Ensure your server accepts POST requests, has a valid TLS certificate, and is not blocking Omnisend's IP addresses.
  • Duplicate headers rejected: Header keys are case-insensitive and must be unique.

Related resources