# Event Queue

The event queue is a Redis List-based pipeline for delivering events from Allegro to external consumers in real time. When enabled, every event tracked by the SDK is pushed onto a per-tenant Redis List. External consumers poll the queue to read events, then issue a separate delete call to remove them.

Each destination that receives a tracked event is independent, and each is controlled by its own feature flag: the Redis queue described here is gated by `UseRedisEvents`, while writing events to the database is gated separately by `StoreEventsInDatabase`. A tenant streaming events off Redis can therefore turn off database storage without affecting the queue.

## How It Works[​](#how-it-works "Direct link to How It Works")

### Feature Flag[​](#feature-flag "Direct link to Feature Flag")

The queue is opt-in per tenant via the `UseRedisEvents` Pennant feature flag.

### Event Ingestion[​](#event-ingestion "Direct link to Event Ingestion")

When an event is tracked via the SDK and the `UseRedisEvents` flag is active, Allegro pushes it onto the tenant's event queue. Each tenant has its own isolated queue. Storing the same event in the database is handled independently by the `StoreEventsInDatabase` flag, so the queue receives events whether or not database storage is enabled.

### Reading Events (`get`)[​](#reading-events-get "Direct link to reading-events-get")

Consumers call `GET /api/platform/events` to read up to 10,000 events from the front of the queue. This is a **non-destructive** read — events remain in the queue until you explicitly delete them.

### Deleting Events (`delete`)[​](#deleting-events-delete "Direct link to deleting-events-delete")

After processing the events, call `DELETE /api/platform/events?count=N` where `N` is the `count` value returned by the preceding `GET`. Passing the exact count prevents a race condition: any events that arrived between the `GET` and the `DELETE` are left untouched and will be returned on the next `GET`.

***

## API Endpoints[​](#api-endpoints "Direct link to API Endpoints")

All endpoints are under the `/api/platform` prefix and require Sanctum authentication (`api` middleware + `auth:sanctum`).

***

### `GET /api/platform/events`[​](#get-apiplatformevents "Direct link to get-apiplatformevents")

Read up to 10,000 events from the front of the queue. Non-destructive — events are not removed until `DELETE` is called.

If the `UseRedisEvents` feature flag is not active for the current tenant, returns an empty result.

**Response `200 OK`:**

```json
{
    "data": {
        "count": 2,
        "events": [
            {
                "id": "9b1f...",
                "type": "page_view",
                "session_id": "7c2a...",
                "audience_member_id": "4d8e...",
                "device_id": "1a6b...",
                "page_hostname": "example.com",
                "page_path": "/articles/hello",
                "page_title": "Hello, world",
                "property_slug": "magazine",
                "utm_source": "newsletter",
                "user_agent": "Mozilla/5.0 ...",
                "ip_address": "1.2.3.4",
                "data": { "any": "event-specific values" },
                "created_at": "2026-02-25T18:00:00+00:00",
                "updated_at": "2026-02-25T18:00:00+00:00"
            }
        ]
    }
}

```

The exact set of keys varies per event — see [Event Payload](#event-payload) below for the full field list and which fields are always present.

**Empty queue response:**

```json
{
    "data": {
        "count": 0,
        "events": []
    }
}

```

***

## Event Payload[​](#event-payload "Direct link to Event Payload")

Each entry in the `events` array is the event as it was recorded at tracking time, serialized directly from its stored attributes. The queue carries this **raw** event — not an enriched or derived form.

Fields are included only when set

A field appears in the payload **only if it was set on the event at tracking time** — not based on whether the value is meaningful. Required and always-derived fields are set on every event (though some, such as `user_agent` and `ip_address`, may be `null`). Optional fields are set only when the SDK sent them, or when they were derived from data that was sent; an optional field that was never sent is **omitted entirely** rather than emitted as `null`. (A field a client explicitly sends as `null` is still present, as `null`.) As a result, payloads vary in shape from one event to the next.

### Always present[​](#always-present "Direct link to Always present")

| Field           | Type            | Description                                                                                                                                                       |
| --------------- | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`            | uuid            | Unique event identifier.                                                                                                                                          |
| `type`          | string          | Event type (e.g. `page_view`, `click`, `purchase`).                                                                                                               |
| `session_id`    | uuid            | The customer session the event belongs to.                                                                                                                        |
| `user_agent`    | string \| null  | Request `User-Agent` header. `null` if the request had none.                                                                                                      |
| `ip_address`    | string \| null  | Originating IP address. `null` if it could not be determined.                                                                                                     |
| `property_slug` | string \| null  | Slug of the [property](/developer/guides/properties.md) the request was made on. `null` when the request used a plain organization host with no property context. |
| `created_at`    | ISO 8601 string | When the event was recorded.                                                                                                                                      |
| `updated_at`    | ISO 8601 string | Equal to `created_at` for queued events.                                                                                                                          |

### Present when set[​](#present-when-set "Direct link to Present when set")

These appear only when the tracking call supplied them (directly or via the tracked URL).

| Field                | Type   | Source                                                                |
| -------------------- | ------ | --------------------------------------------------------------------- |
| `audience_member_id` | uuid   | The authenticated audience member, when known.                        |
| `device_id`          | uuid   | The originating device, when known.                                   |
| `page_hostname`      | string | Host parsed from the tracked URL.                                     |
| `page_path`          | string | Path parsed from the tracked URL.                                     |
| `page_title`         | string | Page title.                                                           |
| `utm_source`         | string | UTM parameter parsed from the tracked URL's query string.             |
| `utm_medium`         | string | UTM parameter parsed from the tracked URL's query string.             |
| `utm_campaign`       | string | UTM parameter parsed from the tracked URL's query string.             |
| `utm_term`           | string | UTM parameter parsed from the tracked URL's query string.             |
| `utm_content`        | string | UTM parameter parsed from the tracked URL's query string.             |
| `content_type`       | string | Type of content the event relates to.                                 |
| `content_id`         | string | Identifier of the content the event relates to.                       |
| `publisher`          | string | Publisher associated with the content.                                |
| `referer`            | string | Referring URL.                                                        |
| `object_type`        | string | Type of the object the event acted on.                                |
| `object_id`          | string | Identifier of the object the event acted on.                          |
| `context`            | string | Free-form context label supplied by the integration.                  |
| `conversion_pv_id`   | string | Page-view event ID attributed as the conversion source.               |
| `url_in_click`       | string | Destination URL for click / link-click events.                        |
| `data`               | object | Arbitrary event-specific key/value data, returned as a nested object. |

note

There is no `url` field

The tracked page URL is never stored or emitted as-is. It is broken apart into `page_hostname`, `page_path`, and any `utm_*` query parameters, and the original URL is then discarded.

Raw, not enriched

The queue reflects the event exactly as captured. Attributes populated by later, asynchronous processing — such as the parsed user-agent breakdown stored in `user_agent_parsed` — are **not** part of the queue payload.

***

### `DELETE /api/platform/events`[​](#delete-apiplatformevents "Direct link to delete-apiplatformevents")

Remove exactly `count` events from the front of the queue. The `count` parameter is **required** and must match the `count` returned by the preceding `GET` call — this prevents accidentally deleting events that arrived after the read.

**Query parameters:**

| Parameter | Type        | Required | Description                                                                |
| --------- | ----------- | -------- | -------------------------------------------------------------------------- |
| `count`   | integer ≥ 1 | Yes      | Number of events to remove. Use the `count` value from the `GET` response. |

**Response `204 No Content`** — events removed.

**Response `422 Unprocessable Content`** — `count` is missing or invalid.

***

## Recommended Consumer Pattern[​](#recommended-consumer-pattern "Direct link to Recommended Consumer Pattern")

```text
loop:
    GET /api/platform/events
    if count == 0 → sleep, continue

    process each event

    DELETE /api/platform/events?count={count}

```

Read all events first, process them, then delete — passing the exact `count` from the `GET` response. Because `GET` is non-destructive, events are safe to re-read if your consumer needs to retry before issuing the `DELETE`. Using the explicit count ensures events that arrived between your `GET` and `DELETE` are never accidentally removed.
