> ## Documentation Index
> Fetch the complete documentation index at: https://docs.omneo.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Receiving and processing Omneo event webhooks in your integration.

Omneo fires webhooks when events occur in the platform, a transaction is created, a profile is updated, a tier is achieved. Configure a webhook endpoint to receive these events and react in your own system.

## What is a webhook?

An Omneo webhook is an HTTP POST from Omneo to a URL you provide. The request body is the [Event Context](/dev-guides/webhooks/event-contexts) for the triggering event, a richer payload than the standard API response, including aggregations, flattened tags, and related objects.

## Configuring a webhook

Webhooks are configured in Omneo CX Manager under **Settings > Webhooks**, or via the [Webhooks API](/api-reference/webhook/add-a-webhook). Each webhook subscribes one URL to one trigger event.

| Field               | Required | Description                                                                                                                                                                      |
| ------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Trigger type        | Yes      | The event that fires the webhook, for example `profile.updated`                                                                                                                  |
| URL                 | Yes      | The endpoint Omneo POSTs to. Each trigger and URL combination must be unique                                                                                                     |
| Namespace           | Yes      | Identifies the integration that owns the webhook. Events caused by the API token matching this namespace are not delivered back to it, which prevents update loops               |
| Queue               | No       | Delivery priority: `high`, `default`, or `low`. Invalid values fall back to `low`                                                                                                |
| Condition           | No       | A [JSON Logic](/dev-guides/reactions/json-conditions) rule evaluated against the event context. The webhook only fires when the rule returns true. See [Conditions](#conditions) |
| Extra data template | No       | A Twig template that adds fields to the payload before delivery. See [Extra data template](#extra-data-template)                                                                 |

<Note>Webhooks deliver the raw event context for a trigger. If you need a custom-shaped payload fired from a Reaction, use a [Target](/dev-guides/reactions/targets) instead. See [Webhooks vs Targets](/concepts/automation/webhooks) for when to use each.</Note>

## Webhook events

| Event                    | Fires when                       |
| ------------------------ | -------------------------------- |
| `profile.created`        | A new profile is created         |
| `profile.updated`        | A profile is updated             |
| `transaction.created`    | A transaction is recorded        |
| `transaction.updated`    | A transaction is updated         |
| `benefit.issued`         | A benefit is issued to a profile |
| `benefit.redeemed`       | A benefit is redeemed            |
| `reward.issued`          | A reward is issued               |
| `reward.redeemed`        | A reward is redeemed             |
| `points.awarded`         | Points are awarded               |
| `tier.achieved`          | A profile achieves a new tier    |
| `achievement.progressed` | An achievement is incremented    |

## Conditions

A **Condition** is a [JSON Logic](/dev-guides/reactions/json-conditions) rule attached to a webhook. Before Omneo queues a delivery, it evaluates the condition against the [Event Context](/dev-guides/webhooks/event-contexts) for the triggering event. The webhook is only sent when the condition returns true.

Conditions run before delivery, so they reduce traffic at the source. Use them instead of receiving every event and discarding the ones you do not need in your own endpoint.

Conditions fail closed. If a condition returns false, or evaluation throws an error (for example a malformed rule), the webhook is not sent.

The same JSON Logic syntax used in [Reaction filters](/dev-guides/reactions/json-conditions) applies here. The `{ "var": "field" }` expression reads a value from the event context, using dot notation for nested fields.

### Filtering by event source

Every event context includes a `requested_by` object describing the API token that caused the event:

```json theme={null}
{
  "requested_by": {
    "id": 42,
    "user_id": 42,
    "token": "pos-sync",
    "host": "[tenant].api.omneo.io"
  }
}
```

The `token` field is the name of the API token that made the request. Use it to skip events caused by a specific integration. For example, to stop a webhook firing for updates made by a token named `pos-sync`:

```json theme={null}
{ "!==": [{ "var": "requested_by.token" }, "pos-sync"] }
```

Or to only deliver events caused by your e-commerce integration:

```json theme={null}
{ "===": [{ "var": "requested_by.token" }, "shopify-integration"] }
```

`requested_by` can be null for events generated by internal platform processes rather than an API request. Provide a default value when comparing so the rule still evaluates:

```json theme={null}
{ "!==": [{ "var": ["requested_by.token", ""] }, "pos-sync"] }
```

<Note>The webhook `namespace` already excludes one source automatically: events caused by the API token whose name matches the namespace are never delivered to that webhook. Use a condition when you need to filter sources other than your own.</Note>

### Filtering by profile data

Any field in the event context can drive a condition. For profile events, profile fields sit at the root of the context.

Only fire when the profile is in the gold tier:

```json theme={null}
{ "===": [{ "var": "tier_handle" }, "gold"] }
```

Only fire for profiles tagged `vip` with more than \$1,000 spend in the last 12 months:

```json theme={null}
{
  "and": [
    { "in": ["vip", { "var": "flattened_tags" }] },
    { ">": [{ "var": "aggregations.spend_12m" }, 1000] }
  ]
}
```

Only fire when email marketing consent is on:

```json theme={null}
{ "===": [{ "var": "attributes.comms.email_promo" }, true] }
```

For transaction events, the profile is nested under `profile`, so the paths become `profile.tier_handle`, `profile.flattened_tags`, and so on. See [Event Contexts](/dev-guides/webhooks/event-contexts) for the fields available per event type.

### Extended operators

Webhook conditions support all standard JSON Logic operators plus Omneo extensions. The most useful for filtering:

| Operator              | Arguments                                    | Returns true when                                                                                                                                 |
| --------------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `not_in`              | `[value, array]`                             | The value is not in the array. On a string, checks the substring is absent                                                                        |
| `non_empty_array`     | `[array]`                                    | The array has at least one element                                                                                                                |
| `array_length`        | `[array, operator, number]`                  | The array length satisfies the comparison, for example `[{"var": "items"}, ">=", 2]`                                                              |
| `array_has_key_value` | `[key, value, data]`                         | Any object in the data, searched recursively, has the key with exactly that value                                                                 |
| `number_equal`        | `[left, right]`                              | Both values are numerically equal, tolerating string-formatted numbers                                                                            |
| `date_filter`         | `[value, operator, num, unit]`               | The date compares true against now plus the offset. Operator is a comparison such as `lt` or `gte`, unit is `days`, `weeks`, `months`, or `years` |
| `compare_dates`       | `[value, compareValue, operator, num, unit]` | The first date compares true against the second date plus the offset                                                                              |

String helpers `to_lower` and `to_upper` normalise values before comparison, for example matching an email domain case-insensitively. For the complete list including date and value helpers, see [Extended operators](/dev-guides/reactions/json-conditions#extended-operators).

## Extra data template

An **Extra data template** is a Twig template attached to a webhook. When the webhook fires, Omneo renders the template with the full event context as variables, parses the output as JSON, and merges the resulting fields into the top level of the payload before delivery.

Use it to enrich the payload with computed or static fields so the receiving system does not need to derive them:

```twig theme={null}
{
  "source_system": "omneo",
  "full_name": "{{ first_name }} {{ last_name }}",
  "is_vip": {{ tier_handle == 'gold' ? 'true' : 'false' }},
  "spend_12m": {{ aggregations.spend_12m|default(0) }}
}
```

For a `profile.updated` event, the delivered payload is the profile event context plus these four extra fields at the root.

### Template rules

* The rendered output must be a valid JSON object. Anything else, including a bare value or malformed JSON, aborts the delivery.
* Extra fields merge at the top level of the payload. A template key that matches an existing context key overrides it.
* All event context fields are available as Twig variables, with the same paths as conditions: `tier_handle`, `aggregations.spend_12m`, `profile.email` on transaction events, and so on.
* Quote string values in the template output. Leave numbers and booleans unquoted.
* Omneo strips newlines from the rendered output before parsing, so do not rely on multi-line string values.
* Use the `default` filter for fields that can be absent, for example `{{ aggregations.spend_12m|default(0) }}`, so a missing value does not produce invalid JSON.

<Warning>If the template fails to render or produces invalid JSON, the webhook is not sent at all. Test templates against a real event context before relying on them in production.</Warning>

### Example: routing hints for a comms platform

A template can precompute routing decisions so the receiver stays simple:

```twig theme={null}
{
  "list_id": "{{ tier_handle == 'gold' or tier_handle == 'platinum' ? 'vip-list' : 'standard-list' }}",
  "consent": {
    "email": {{ attributes.comms.email_promo ? 'true' : 'false' }},
    "sms": {{ attributes.comms.sms_promo ? 'true' : 'false' }}
  }
}
```

## Delivery behaviour

* Profile webhooks (`profile.created`, `profile.updated`) are not dispatched for incomplete profiles (`is_completed` is `0`) or for profiles with a `system` profile type.
* Webhooks are never delivered back to the integration that caused the event when the requester's API token name matches the webhook `namespace`.
* Deliveries are queued by priority (`high`, then `default`, then `low`).
* The condition is evaluated before the delivery is queued. The extra data template is rendered at send time.
* Each event is delivered once. Omneo does not retry failed deliveries, so reconcile against the API to catch events your endpoint missed, for example by polling [Browse transactions](/api-reference/transaction/browse-transactions) with an `updated_at` filter.

## Processing a webhook

Your endpoint should:

1. Respond with `200 OK` quickly, process asynchronously if needed
2. Validate the payload structure matches your expected event context
3. Identify the profile via the `id` field in the context
4. Take the appropriate action in your system

## Example: Transaction webhook context

```json theme={null}
{
  "id": "txn-uuid",
  "profile_id": "profile-uuid",
  "total": 149.95,
  "transacted_at": "2025-06-15 14:32:00",
  "profile": {
    "id": "profile-uuid",
    "first_name": "Jane",
    "last_name": "Smith",
    "email": "jane@example.com",
    "tier_handle": "gold",
    "aggregations": {
      "spend_12m": 2450.00,
      "shop_count": 14
    }
  }
}
```
