Airship Loader
The Airship loader pulls granular, row-level engagement events from Airship’s Real-Time Data Streaming (RTDS / “Connect”) feed into your data warehouse. Every event — a send, open, click, close, rich or in-app message interaction, custom conversion, or control (holdout) — is captured individually and attributed to the specific push that a journey, automation, or one-off campaign triggered.
This is the right source when you need engagement feedback at the message level: which journey-triggered pushes were delivered, opened, and converted, per user and per variant. It is not the aggregate Reports API — it emits one warehouse row per event.
Prerequisites
- An Airship project with Real-Time Data Streaming (RTDS) enabled. RTDS is a paid add-on — contact Airship to enable it for your project if it is not already active.
- An RTDS access token (see Authentication).
- Your project’s app key.
- Knowledge of your project’s data-residency region (US or EU).
- A connected Warehouse (target warehouse) with write permissions on the target schema.
Authentication
The Airship loader uses token authentication against the RTDS event stream. Two values are required.
Obtaining an RTDS Access Token
- Log in to the Airship dashboard and select your project.
- Open Settings > Project settings > Real-Time Data Streaming.
- Under Direct Integration, click Create Access Token.
- Copy the token immediately — it is shown once and cannot be retrieved later.
- In Zeotap, paste it into the RTDS Access Token field.
Finding your App Key
Your App Key identifies the project. Find it under Settings > Project settings > APIs & Integrations > App keys & secrets. Paste it into the App Key field.
The loader sends these as Authorization: Bearer <access token> and X-UA-Appkey: <app key> on every request.
Configuration
| Setting | Description | Default |
|---|---|---|
| RTDS Access Token | Real-Time Data Streaming access token (Direct Integration) | — (required) |
| App Key | Your Airship project/app key | — (required) |
| Data Region | Data-residency region — US (connect.urbanairship.com) or EU (connect.asnapieu.com) | US |
| Initial Position | Where to start on the first sync, before any offset is saved. EARLIEST backfills the available data window (~last 7 days); LATEST captures only new events from now on | EARLIEST |
| Event Types | Which event types to capture. Defaults to the engagement/attribution set; narrow or widen as needed | Engagement set (below) |
| Max Poll Seconds | Safety cap on how long a single sync reads the stream before stopping and checkpointing | 300 |
| Max Events Per Run | Safety cap on how many events a single sync ingests before stopping and checkpointing | 1000000 |
The default Event Types set covers everything with push/journey attribution plus custom conversions: SEND, PUSH_BODY, OPEN, CLOSE, CONTROL, RICH_DELIVERY, RICH_READ, RICH_DELETE, IN_APP_MESSAGE_DISPLAY, IN_APP_MESSAGE_RESOLUTION, IN_APP_MESSAGE_EXPIRATION, WEB_CLICK, WEB_SESSION, CUSTOM.
Available Objects
The loader exposes a single stream. Each event type lands as a row in the same table, discriminated by the type column.
| Object | API Name | Description | Default Sync Mode |
|---|---|---|---|
| Engagement Events | events | Row-level RTDS events (sends, opens, clicks, closes, rich/in-app, custom, control), each attributed to the push/journey that triggered it | Incremental |
Key Columns
The events table flattens the event envelope, the device/user identity, and the union of engagement body fields. Notable columns:
| Column | Type | Description |
|---|---|---|
id | string | Unique event ID — de-duplicate on this (delivery is at-least-once) |
offset | string | Opaque stream cursor (the incremental high-watermark) |
occurred | datetime | When the event happened |
processed | datetime | When Airship ingested the event |
type | string | Event type (SEND, OPEN, CLOSE, CUSTOM, …) |
device_channel | string | Airship channel ID (device) |
device_named_user_id | string | Named user ID — your customer identifier |
body_push_id | string | The push that produced this event |
body_group_id | string | The automation/journey/sequence group (fans out to many pushes) |
body_variant_id | integer | A/B test variant number |
from_automation | boolean | Derived — true when the event is attributed to an automation/journey group (body_group_id, or the direct-open triggering_push group) |
body_triggering_push_push_id | string | Direct-open attribution — the push the user tapped |
body_last_delivered_push_id | string | Influenced-open attribution — the most recent push delivered |
body_name / body_value | string / number | Custom event name and value |
body_campaigns | json | Campaign categories you assigned |
body_raw | json | The complete event body — nothing is lost |
Attributing an event to a journey
Airship does not label events “journey” directly; you infer it from the body:
body_group_idpresent → the send was delivered as part of an automation, sequence, or journey (one group ID fans out to manypush_ids). Thefrom_automationcolumn surfaces this as a boolean.body_push_idpresent with nobody_group_id→ a one-off push sent at a defined time.body_variant_idpresent → the push was part of an A/B test.- A
CONTROLevent (type = 'CONTROL') → the device was held out of that push/experiment — filter on the event type (withbody_group_id) to build the holdout cohort for uplift measurement.
Sync Modes
- Incremental (default) — The stream is resumable by
offset. Zeotap saves the highestoffsetit has read and passes it back asresume_offseton the next sync, so each run picks up exactly where the last left off. This is the only supported mode. - First run — Uses the Initial Position setting (
EARLIESTorLATEST) instead of an offset.
Data Window & High Volume
This loader consumes Airship’s live event stream, which is designed for near-real-time ingestion — capturing engagement events as they happen. Two limits of the stream are worth understanding up front:
- Resumable window — ~7 days or 100 GB, whichever comes first. Airship retains only a rolling data window; a saved offset (or an
EARLIESTstart) can reach back at most this far. There is no way to replay older row-level events through the stream. - Event-age limit — 90 days. Airship does not process events whose occurrence timestamp is older than 90 days.
Because of this, the loader is best run on a frequent schedule so it never falls behind the window. It does not backfill months of history — that data is not available from the stream. (Aggregate historical reporting is available separately through Airship’s Reports; a one-time row-level historical export is an Airship Support engagement.)
Sizing runs for high volume
The Max Events Per Run and Max Poll Seconds settings are configurable safety caps — a run normally stops earlier when it catches up to now, but these bound a run on a project busy enough that the stream never goes idle. The defaults (1,000,000 events / 300 seconds) comfortably drain a 30–60 minute window at ~10M events/day (~417k/hour).
- If a single scheduled window exceeds what one run can drain, the loader checkpoints its offset and the next run continues — no data is lost; catch-up simply spreads across runs.
- The real ceilings on a single run are the job’s execution time limit, its memory budget, and your warehouse’s write throughput.
- For very high steady-state volume, raise the caps and/or sync more frequently so throughput keeps pace with your event rate. A run that keeps hitting the caps every time is the signal to increase them or shorten the schedule interval.
How It Works
- Streaming, not paging — RTDS returns an unending stream of newline-delimited JSON, one event per line. The loader reads it line by line, flattens each event, and writes rows in batches — it never buffers the whole stream in memory.
- Cursor checkpointing — The
offsetis the incremental cursor. The loader tracks the maximum offset seen and checkpoints it after each batch; Zeotap persists it between runs. - Bounding a scheduled run — Because the stream never ends on its own, each sync self-terminates when it has drained to “now” (Airship sends an idle keep-alive /
OFFSET_UPDATE), or when it hits the Max Poll Seconds or Max Events Per Run cap. Whatever the stop reason, the latest offset is saved so the next scheduled run resumes cleanly. - At-least-once delivery — The stream occasionally repeats an event; duplicates share the same
idbut have a differentoffset. The loader de-duplicates onidwithin a run, but events can still repeat across runs. De-duplicate onidin your models for exactly-once semantics.
Schema Mapping
| Airship Type | Warehouse Type | Notes |
|---|---|---|
ISO-8601 timestamp (occurred, processed, *_time) | TIMESTAMP | Parsed and stored as UTC |
variant_id | BIGINT | A/B variant number |
value (custom events) | DOUBLE | Numeric custom-event value |
alerting, trimmed, from_automation | BOOLEAN | |
identifiers, attributes, campaigns, context, properties, body_raw | JSON / VARCHAR | Nested objects stored as JSON |
| everything else | VARCHAR | IDs, channels, event type, session |
Rate Limits
RTDS is designed for high-volume streaming and does not publish a fixed request quota. The loader honors backoff on throttling (HTTP 429) and 5xx responses. The resumable data window holds roughly the last 7 days (or 100 GB) of events — a saved offset older than that may no longer be resumable, in which case the sync restarts from the Initial Position.
Troubleshooting
| Issue | Solution |
|---|---|
| ”Real-Time Data Streaming unavailable” / HTTP 402 | RTDS is a paid add-on — contact Airship to enable it, then create a Direct Integration access token. This status is also returned if you exceed the allowed number of simultaneous connections |
| ”No data for this app key” / HTTP 404 | Airship has no stream data for the supplied app key — verify the App Key and that the Data Region (US/EU) matches your project |
| ”Authentication failed” / HTTP 401 | Verify the RTDS access token and app key. The token is shown only once at creation — recreate it if it was lost |
| HTTP 406 on connect | The API version header was rejected — this is a loader-side issue; contact support |
| No new rows on a run | If nothing happened in your project since the last sync, there is nothing to ingest. The offset is unchanged and the next run resumes correctly |
| Duplicate rows | Delivery is at-least-once. De-duplicate on the id column in your models |
| Backfill returns fewer events than expected | The resumable window is ~7 days / 100 GB. Older events are outside the stream’s horizon |
Some events have no push_id/group_id | Events not originated by an Airship push (e.g. app-defined in-app messages, some custom events) carry no push attribution — this is expected |
| A run stops before draining the whole backlog | It hit the Max Poll Seconds or Max Events Per Run cap. The offset is checkpointed and the next scheduled run continues — no data is lost. Raise the caps or sync more frequently for very high volumes |
| Need to load more than ~7 days of history | The live stream only retains ~7 days / 100 GB and won’t replay older events. Run the loader frequently so it stays within the window; for one-time row-level history beyond that, contact Airship Support |
Next Steps
- Create a model to transform raw Airship events — de-duplicate on
id, then joinbody_group_idto your journey definitions - Measure journey engagement by aggregating
OPEN/CUSTOMevents perbody_group_idandbody_variant_id - Compare
CONTROL(holdout) cohorts against exposed cohorts for uplift - Sync enriched engagement data back to your activation tools