Skip to main content
Enrichment lets Airdun read customer data that only your systems have — plan name, seat count, lifetime value, feature usage — and use it when building a recovery plan. Two sources are available; a workspace uses one at a time:
  • Your own endpoint — you expose an HTTP endpoint, Airdun calls it and maps the JSON response onto named fields you define.
  • Stripe metadata — you sync your data onto the metadata of your Stripe Customer and Subscription objects, and Airdun reads it from Stripe directly. Nothing to host.
Either way, the values go through the same field mapping and are stored on the recovery case. Switching source keeps the other configuration intact, so you can move back without reconfiguring anything.
Airdun is the client. There is no ingest API and no way to push data to Airdun. Enrichment is a read, performed by Airdun — against an endpoint you host, or against the metadata you keep on Stripe.

When Airdun reads your data

Whichever source is active, it is read at two moments — never once per message, and never on a fixed schedule:
  1. At case creation. The response is projected into a snapshot written onto the case. Every downstream step reads that snapshot.
  2. Refresh during the case. When Airdun’s recovery engine evaluates an open case and the snapshot is older than 6 hours, the source is read again and the snapshot is replaced. On these refresh calls the timeout is capped at 4 seconds, even if you configured a longer one.
A refresh that fails — timeout, non-2xx, invalid JSON, a Stripe read error — keeps the previous snapshot, including its original timestamp. A stale value is preferred over no value; the error is recorded on the case and in the call log. Because evaluations happen when something changes on the case (a message sent, a Stripe retry, a customer event), calls are bursty rather than periodic: a quiet case may not be refreshed for days, and the 6-hour floor guarantees a burst of activity never hits your endpoint repeatedly. A new failed payment opens a new case, which triggers a new read at creation. Each case records which source its snapshot came from, and the case view shows it.

Reading from Stripe metadata

If you already sync your data to Stripe — or would rather not host an endpoint — Airdun can read the metadata you keep on your Stripe objects instead. For each read, Airdun fetches the customer and one subscription live from Stripe, through the Stripe connection you already granted, and assembles them into a single JSON document your field paths are evaluated against:
  • Paths start at the document root: $.customer.metadata.team_size, $.subscription.metadata.plan.
  • The subscription is the one attached to the case’s failing invoice; outside a case (tests, previews), the customer’s most recent one. subscription is null when the customer has none.
  • There is nothing to host, authenticate, or rotate — the read rides on the existing Stripe connection.

Limits and value formats

Stripe’s own metadata limits apply: 50 keys per object, 40 characters per key name, 500 characters per value — and every value is a string. Store values so they survive type coercion: Keep metadata keys identifier-shaped (plan_tier, not plan-tier) so the path syntax can reach them.

Freshness

Airdun reads Stripe live at case creation and on refresh, but the values themselves are whatever your last sync wrote. If your sync lags, recovery runs on lagged data — Airdun has no way to detect this, and the snapshot timestamp reflects the read, not your sync. The endpoint source does not have this limitation.

Testing the sync

Use Test Stripe sync in the dashboard: pick a real customer and Airdun shows the raw customer and subscription document alongside the projected fields. The field dialog also includes a metadata explorer that lists the keys actually present on a sample customer, so you can map them without guessing. If a test finds no metadata at all, your sync has most likely never written to Stripe.

Your own endpoint: the contract

URL

Your URL must contain the {customer_id} placeholder:
  • The placeholder is replaced with the Stripe customer ID (cus_…), URL-encoded.
  • Only the first occurrence of {customer_id} is substituted.
  • HTTPS is required. Plain HTTP is accepted only for localhost and 127.0.0.1.
  • Maximum length: 2048 characters.

Authentication

Three modes are available. Airdun stores your secret encrypted at rest (AES-256-GCM).
No authentication header is sent. Only appropriate if your endpoint is protected some other way — the Stripe customer ID is not a secret.
Airdun sends:
Airdun sends your secret as the raw value of a header you name — no Bearer prefix, no scheme:
The header name is required when this mode is selected. Maximum 128 characters.

Custom headers

You can add up to 20 additional headers (name ≤ 128 chars, value ≤ 1024 chars).
Custom headers are applied after the authentication header. A custom header whose name collides with your auth header — Authorization, or whatever you named your API key header — will silently overwrite it, and your endpoint will reject the call.

Timeout and failure

  • Timeout is configurable from 500 ms to 30 000 ms. Default: 3000 ms. The configured value applies in full at case creation; refresh calls cap it at 4000 ms.
  • There are no retries. Each call is a single request. If it fails, it fails — the next opportunity is the next refresh.
Enrichment never blocks recovery. If your endpoint times out, returns a non-2xx status, or returns a body that is not valid JSON at case creation, Airdun records the error on the case and the recovery case is created anyway with an empty snapshot. When the snapshot is empty:
  • The strategy and message-generation models receive no enrichment context.
  • Escalation rules that reference an enrichment.* field are skipped. Rules that do not reference enrichment still evaluate normally.
A response that is valid JSON but does not match your field mapping is not an error. Unmatched paths simply resolve to null, and the case proceeds silently. Check the call log in your dashboard if fields arrive empty.

Mapping the response to fields

Airdun does not care about your response shape. You define up to 100 fields, each of which extracts one value from the JSON. The mapping works the same for both sources — with the endpoint, paths run against your response body; with Stripe metadata, they run against the { customer, subscription } document shown above. Every field is optional. A path that does not resolve yields null for that field and nothing else happens — the other fields are unaffected and the case proceeds. There is no way to declare a field mandatory, and a missing field is never an error.

Supported path syntax

The path syntax is a deliberately small subset and is not general-purpose JSONPath. A path must match:
In practice, this means only two operations, starting from $:
  • Dot access on identifier-shaped keys — $.plan.name
  • Numeric array indexing$.seats[0].email
Wildcards ($.items[*]), filter expressions ($.items[?(@.active)]), recursive descent ($..name), slices, and bracket-quoted keys ($['my-key']) are not supported and will be rejected.A key that is not identifier-shaped — total-revenue, 2fa_enabled — is unreachable. Rename it in your response, or expose an alias.

Type coercion

The declared type controls how the extracted value is coerced. Anything that cannot be coerced becomes null.

Where enriched data is used

Once snapshotted, your fields feed the same consumers whichever source produced them:

Recovery strategy

Fields are passed to the model that designs the recovery plan, as a name → value map. They influence channel ordering, touchpoint count, and timing.

Message generation

The same context is available to the model that writes each message.

Escalation rules

Fields are addressable as enrichment.<field_name> in escalation conditions — for example enrichment.plan.

Language resolution

One field can be nominated to carry the customer’s language. See below.

Phone resolution

One field can be nominated to carry the customer’s phone number, used for SMS and WhatsApp outreach. See below.
Enriched values are passed to the model as context. They are not template variables — there is no {{enrichment.field}} interpolation, and you cannot force a value to appear verbatim in a message.

Language field

You can nominate one of your fields as the language field. Its name must match an existing field name. Airdun resolves a customer’s language in this order, taking the first that yields a value:
  1. The language declared on the customer in Airdun
  2. Stripe’s preferred_locales
  3. Your nominated enrichment field
  4. A country-to-language mapping
  5. The workspace fallback language

Phone field

You can also nominate one field as the phone field. Its name must match an existing field, and that field must be of type STRING — a NUMBER field would drop the leading + or 0 and the value would silently fail to parse. When the field resolves to a valid phone number, it takes priority over the phone number on the Stripe customer for SMS and WhatsApp outreach. A value that does not parse to a usable number ("N/A", a malformed string) is ignored and the Stripe phone is used instead.

Example

Your endpoint:
Your response:
A matching field mapping: With locale nominated as the language field, and an escalation rule on enrichment.revenue_ltv_usd.

Testing and observability

Use Test endpoint (endpoint source) or Test Stripe sync (Stripe source) in the dashboard to fire a real read with a customer of your choice. This performs an actual call and shows you the raw response alongside the projected fields — the fastest way to check a path, an auth header, or whether your sync writes what you think it writes. Every read Airdun performs is recorded, including failures, with its status, duration, source, and error category (timeout, network, http_4xx, http_5xx, http_other, stripe_error, or unknown). Each entry is tagged with its trigger — case creation, mid-case refresh, or a manual test. Review them in the call log in your dashboard. Enrichment can also be switched off without deleting the configuration: the source, auth, and field mappings are kept, but no reads are made and new cases open with an empty snapshot.
Recovery cases fired from the playground do not read either source — the customer is synthetic, so the lookup would be meaningless. The snapshot for those cases is whatever the playground supplies.

Implementation notes (endpoint source)

  • Respond fast. The default budget is 3 seconds with no retry. Serve from a cache or a precomputed table rather than doing expensive joins on the request path.
  • Handle unknown IDs. Stripe customer IDs may exist that your system has no record of. Return 2xx with the fields you have rather than a 404 — a non-2xx discards the entire snapshot, including fields you could have supplied.
  • Keep keys identifier-shaped so the path syntax can reach them.
  • Do not gate on the customer ID alone. It travels in the URL and is not a secret. Use BEARER or API_KEY.