> ## 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.

# System webhooks

> How Klaviyo system webhooks are processed and which Omneo comms preferences each event changes.

Klaviyo **system webhooks** are the standard webhooks that Klaviyo fires when a profile bounces, subscribes, unsubscribes, or is suppressed. The extension registers these webhooks automatically on install. They are not visible to end users in the Klaviyo UI and do not need to be configured manually.

When a system webhook arrives, the extension finds the matching Omneo profile, combines any consent changes from the event, updates the Omneo profile's communication preferences, and records an **interaction** so the activity is tracked on the profile timeline.

## Processing flow

1. **Receive the webhook** at the extension's system webhook endpoint and validate it.
2. **Queue the work** on the inbound Cloud Tasks queue so processing happens in the background.
3. **Find the Omneo profile** using the stored Klaviyo identity, or email as a fallback. If no profile is found, the event is skipped.
4. **Filter circular events**: Any event where Klaviyo's `custom_method_detail` is `omneo` was originally caused by Omneo itself and is silently ignored to prevent loops.
5. **Combine events** for the same profile and merge their consent changes into a single payload.
6. **Stamp consent timestamps**: `email_consent_updated_at` and `sms_consent_updated_at` are added whenever the corresponding consent fields change. Timestamps are written in `Y-m-d H:i:s` format.
7. **Update Omneo** with the combined comms payload.
8. **Master list check**: If a [master list](/extensions/klaviyo/master-list) is configured, the profile is added to it if they are now subscribed.
9. **Create interactions**: One interaction is created per event, with a positive, neutral, or negative signal.

<Note>
  Subscription status is never pushed back to Klaviyo from a system webhook. Klaviyo already made the change on its side and is the source of truth for that event, so pushing back would create a sync loop.
</Note>

## Event reference

The table below summarises what each system webhook does to the Omneo profile. **Comms changes** lists the fields that are written; **Signal** indicates the type of interaction recorded.

### Email events

| Event                                        | Comms changes                                                              | Signal   |
| -------------------------------------------- | -------------------------------------------------------------------------- | -------- |
| `bounced_email` (hard)                       | `email_bounced: true`, `email_promo: false`, `email_optout: true`          | Neutral  |
| `bounced_email` (soft)                       | None (interaction only)                                                    | Neutral  |
| `clicked_email`                              | None                                                                       | Positive |
| `clicked_email_to_unsubscribe`               | `email_promo: false`, `email_optout: true`                                 | Neutral  |
| `dropped_email`                              | None                                                                       | Neutral  |
| `manually_suppressed_from_email_marketing`   | `email_promo: false`, `email_optout: true`                                 | Negative |
| `manually_unsuppressed_from_email_marketing` | `email_promo: true`, `email_optout: false`, `email_bounced: false`         | Positive |
| `marked_email_as_spam`                       | `email_promo: false`, `email_optout: true`                                 | Negative |
| `subscribed_to_email_marketing`              | `email_promo: true`, `email_optout: false`, `email_consent_updated_at` set | Positive |
| `unsubscribed_from_email_marketing`          | `email_promo: false`, `email_optout: true`, `email_consent_updated_at` set | Negative |
| `bounce_suppression_added`                   | `email_promo: false`, `email_optout: true`                                 | Negative |
| `invalid_email_suppression_added`            | `email_bounced: true`, `email_optout: true`                                | Negative |

### SMS events

| Event                                      | Comms changes                          | Signal   |
| ------------------------------------------ | -------------------------------------- | -------- |
| `clicked_sms`                              | None                                   | Neutral  |
| `failed_to_deliver_sms`                    | `sms_bounced: true`                    | Neutral  |
| `failed_to_deliver_automated_response_sms` | None                                   | Neutral  |
| `subscribed_to_sms_marketing`              | `sms_promo: true`, `sms_optout: false` | Positive |
| `subscribed_to_sms_transactional`          | `sms_optout: false`                    | Positive |
| `unsubscribed_from_sms_marketing`          | `sms_promo: false`                     | Negative |
| `unsubscribed_from_sms_transactional`      | `sms_optout: true`                     | Negative |

### Push events

| Event          | Comms changes        | Signal  |
| -------------- | -------------------- | ------- |
| `bounced_push` | `push_bounced: true` | Neutral |

## Combined event example

If a single batch contains a hard bounce and an unsubscribe for the same profile, the extension merges them into a single Omneo update:

```text theme={null}
Events:
  1. bounced_email (Hard)
  2. unsubscribed_from_email_marketing

Combined Omneo update:
  email_bounced = true
  email_promo = false
  email_optout = true
  email_consent_updated_at = 2024-01-15 10:30:00
```

This avoids redundant API calls and ensures the resulting Omneo profile state is consistent.
