Aura LogoAura

Payload Reference

Field-level reference for every webhook payload

Payload Reference

Every Aura webhook ships in the same envelope. The data object's shape depends on the event type. This page documents each shape exactly as it appears in production.

The interfaces below mirror apps/api/src/lib/webhook-events.ts — the canonical source of truth. Fields here that show string | null may arrive as null if the underlying data is missing.

Envelope

interface WebhookPayload<T> {
  event: WebhookEventType;     // e.g. "call.booked"
  created_at: string;          // ISO 8601, UTC
  organization_id: string;     // The org the event belongs to
  data: T;                     // Resource payload — see below
  idempotency_key?: string;    // Stable across retries — see below
}
FieldTypeNotes
eventstringOne of the 15 event types
created_atstringISO 8601 timestamp of emission, UTC. Recomputed on each retry — do not dedup on this.
organization_idstringYour Aura organization ID
dataobjectShape depends on event
idempotency_keystringStable per-event key, identical across retries of the same event. Use it to enforce exactly-once.

Exactly-once with idempotency_key

Delivery is at-least-once: if your endpoint commits its side effect and then crashes (or times out) before returning a 2xx, Aura re-POSTs the same event. The retry carries the same idempotency_key — created_at and the signature timestamp are recomputed, but the key is not.

Enforce exactly-once by storing the key and rejecting duplicates:

-- One row per (subscription, event). The conflict drops the redelivery.
CREATE UNIQUE INDEX uq_webhook_dedup
  ON processed_webhooks (subscription_id, idempotency_key);
// On each delivery, insert-or-ignore before processing.
const inserted = await db.query(
  `INSERT INTO processed_webhooks (subscription_id, idempotency_key)
   VALUES ($1, $2) ON CONFLICT DO NOTHING RETURNING 1`,
  [subscriptionId, payload.idempotency_key],
);
if (inserted.rowCount === 0) {
  return res.status(200).send('OK'); // already processed — drop silently
}
// ... process the event exactly once ...

The key is optional in the type only for forward compatibility — every envelope delivery populates it in practice. If you ever see it missing, fall back to processing the event normally (at-least-once) rather than dropping it.


LeadWebhookData

Emitted on lead.created, lead.updated, lead.status_changed.

FieldTypeDescription
idstringLead ID
namestring | nullFull name, if provided
emailstring | nullEmail, if provided
phonestring | nullPhone, if provided
companystring | nullCompany, if provided
statusstringThe lead's stored status (see status values below). It can lag behind the board: use pipeline_stage to show where the lead stands.
pipeline_stagestring | nullThe lead's column on the Opportunities board when the event was sent, worked out from its calls (see values below). null only if Aura could not read it.
timezonestring | nullIANA timezone, if known
sourceobject | nullAttribution — see below
consentarrayEvery consent box and what the person chose (see below). agreed is true only while they agree, for the lead's phone now. The phone number is sent either way, so check this before you text. [] when no consent is on file.
consent_by_typeobjectOne answer for each kind of message: yes, no or not_asked (see below). Every kind reads not_asked when no consent is on file, except Booking texts on some older leads (see below).
created_atstringWhen the lead was created (ISO 8601)
updated_atstringWhen the lead was last updated (ISO 8601)

source object

FieldTypeDescription
utm_sourcestring | nullTraffic platform (e.g. google, facebook)
utm_mediumstring | nullChannel type (e.g. cpc, email, social)
utm_campaignstring | nullCampaign name
utm_termstring | nullPaid search keywords / targeting criteria
utm_contentstring | nullAd creative or A/B variant
referrerstring | nullHTTP referrer — embed host page or traffic source

One entry per consent box, in the order Aura recorded them. This includes your own boxes on the booking form. Aura's box for appointment texts is in the list once the person has seen it, or replied STOP or START.

FieldTypeDescription
idstringWhich box this is, stable when its wording changes: the box's own id for a box your team added, aura_sms for Aura's box for appointment texts, data_consent for the data-processing box. Match on this, not on label
kindstringcustom (a box your team added), aura_sms (Aura's box for appointment texts) or data_consent (the data-processing box)
labelstringThe words on the box, as the person saw them
disclosurestring | nullThe fine print shown beside the box: for Aura's box for appointment texts, how often texts come, that rates may apply, and how to stop them. null for other boxes
agreedbooleanTrue only while they agree, for the lead's phone now. False if they never ticked the box, took it back, or agreed for a different number
statestringgranted (ticked), withdrawn (agreed, then took it back by unticking the box or replying STOP) or declined (finished the form without ticking it)
atstringWhen they made the current choice (ISO 8601)
granted_atstring | nullFor a withdrawn box, when they first agreed. null otherwise
phonestring | nullThe phone number they made the choice for. If it is not the lead's phone now, agreed is false. null for a choice made before Aura kept the number
sourcestring | nullHow the choice reached Aura: booking_form, sms_reply_stop, sms_reply_start, carrier_block, admin or import. null when Aura did not record how
linksarrayThe links the box showed, each with text and url

Before you text anyone, keep these in mind:

  • An empty list means no consent is on file. That is not the same as declined, but it is still not a yes. Treat it as no consent. One exception: for Booking texts, check consent_by_type.sms_booking, which can read yes or no on some older leads whose list is empty (see consent_by_type).
  • agreed is false when they agreed for a different number. phone says which number that was.
  • Consent can change after the webhook is sent. For example, the person may reply STOP. Read the lead again with GET /v1/leads/:id right before you text them.
  • A box they did not tick is only recorded once they finish the form. Someone who leaves halfway has no declined entry yet.

One answer for each kind of message, so you can skip people who did not agree without reading each box's wording. Your team chooses which consent box asks about each kind, in Settings. Until a box has a kind, only Booking texts can have an answer, from Aura's own box for appointment texts. Every other kind reads not_asked.

FieldTypeDescription
sms_bookingobjectBooking texts: texts about their booked call, like reminders
sms_marketingobjectMarketing texts: texts with offers and promotions
email_marketingobjectMarketing emails: emails with offers and newsletters
data_processingobjectData use: agreeing to your privacy policy, in a box your team added

Each answer has the same four fields:

FieldTypeDescription
statestringyes (they agreed), no (they left the box unticked or took it back, for example by replying STOP; for texts, also when they agreed for a different phone number) or not_asked (no answer on file)
atstring | nullWhen the answer last changed (ISO 8601, UTC). null when not asked
sourcestring | nullbox (one of your consent boxes) or aura_sms_opt_in (Aura's own box for appointment texts, which a STOP reply turns off). null when not asked
idstring | nullThe box the answer came from, the same id as in consent. null when not asked. On some older leads, Booking texts names Aura's box before consent lists it (see below)
  • not_asked is not a yes. It means nothing the person answered asks about that kind of message. Treat it as no consent.
  • Booking texts can come from Aura's own box. When your booking page shows Aura's box for appointment texts, its answer comes first, so a STOP reply reads as no.
  • Every surface sends the same answers for the same lead: the REST API, GraphQL (Lead.consentByType), the lead webhooks, Zapier and your CRM.
  • Booking texts also reads Aura's texting settings. Some older leads have their choice on Aura's box for appointment texts, or their STOP reply, only in Aura's texting settings. Their Booking texts answer reads that choice, as your CRM does, with source aura_sms_opt_in and at set to when they chose. consent lists only the answers Aura has stored, so on these leads it can be empty or leave out the box that Booking texts' id names. Aura adds that box when it next sends the lead to your CRM. lead.deleted already lists it.
  • If Aura cannot check your booking page's settings at that moment, for example during an outage, Booking texts reads Aura's own box first for a lead who answered it. That way a STOP reply never reads as yes.

Lead pipeline_stage values

The same columns, in the same order, as the Opportunities board.

ValueMeaning
interestedStarted booking, no call yet
disqualifiedFiltered out by the booking questions
qualifiedPassed the booking questions, no call yet
bookedHas a call coming up that they have not confirmed
confirmedHas a call coming up and said they will come
declinedThe calendar invite was declined
cancelledThe call was cancelled
attendedThe call happened, outcome not recorded yet
unfitHad the call, not a fit
no_showBooked but did not turn up
follow_upHad the call, needs a follow-up
lostHad the call, did not buy
wonBought

status and pipeline_stage can disagree for a while. When a guest accepts the invite, pipeline_stage says confirmed at once, while status can still say booked until Aura's status sync updates it. pipeline_stage is null only if Aura could not read the lead's calls.

Lead status values

ValueMeaning
interestedContact info captured, no booking yet
qualifiedForm questions answered, fit confirmed
scheduledCall booked, in the future
attendedCall completed
closedDeal won
lostDeal lost
canceledBooking canceled
disqualifiedMarked as not a fit

CallWebhookData

Emitted on call.booked, call.updated, call.started, call.completed, call.canceled, call.rescheduled, call.no_show.

FieldTypeDescription
idstringCall ID
lead_idstringAssociated lead ID
closer_idstring | nullAssigned host's user ID
booking_link_idstring | nullWhich funnel / product the call came through
scheduled_atstringScheduled meeting time (ISO 8601)
durationnumber | nullScheduled duration in minutes
statusstring | nullCall status (scheduled, attended, no_show, canceled, etc.)
conferencing_urlstring | nullVideo conference URL
utm_sourcestring | nullAttribution: traffic platform
utm_mediumstring | nullAttribution: channel type
utm_campaignstring | nullAttribution: campaign name
utm_termstring | nullAttribution: paid search keywords
utm_contentstring | nullAttribution: ad creative or A/B variant
referralstring | nullPartner / affiliate identifier (separate from utm_source)
guest_rsvp_statusstring | nullGuest's calendar RSVP (yes, no, maybe, noreply)
guest_rsvp_confirmed_atstring | nullWhen the guest first RSVP'd yes
created_atstringWhen the call was created
updated_atstringWhen the call was last updated

Call attribution fields are flat on the call payload (utm_source etc.), whereas lead attribution is nested under source. This intentional asymmetry mirrors the underlying schema: calls inherit UTM at booking time from the originating lead, but are stored as columns for fast pipeline filtering.


call.cancelled payload

call.cancelled is the booking funnel's cancel event, and it is the one event that does not use the envelope above. Its body is the flat booking payload: the call's fields sit at the top level, with "event": "call.cancelled", and there is no data wrapper. Verify it with X-Aura-Signature-V1; it does not carry the legacy X-Aura-Signature.

It replaces call.canceled, which takes no new subscriptions. Moving a subscription from call.canceled to call.cancelled changes the body as well as the event name, so update how your handler reads it.


LeadDeletedWebhookData

Sent on lead.deleted. It holds the lead as it was when Aura removed it. You can't look the lead up after this, so this is the last copy of these fields.

FieldTypeDescription
idstringThe deleted lead's ID. Same as lead_id
lead_idstringThe deleted lead's ID
full_namestring | nullThe lead's name when it was deleted. null when the person asked to be erased
emailstring | nullThe lead's email when it was deleted. null when the person asked to be erased
phonestring | nullThe lead's phone number when it was deleted. null when the person asked to be erased
consentarrayThe lead's consent choices, the same entries as on LeadWebhookData. Empty when the person asked to be erased
consent_summarystring | nullThe same choices as text, one line per box
consent_by_typeobjectThe lead's last answer for each kind of message, the same object as on LeadWebhookData. Every kind reads not_asked when the person asked to be erased
reason"deleted" | "erased"deleted: an admin deleted the lead. erased: the person asked us to erase their data
deleted_atstringWhen Aura removed the lead
event, org_id, timestamp, aura_versionstringThe same record Zapier receives, so one handler can read both

CallCloserReassignedWebhookData

Emitted on call.closer_reassigned. This is the only call event with a non-CallWebhookData shape — it carries the swap details rather than the full call record.

FieldTypeDescription
call_idstringAffected call ID
previous_closer_idstring | nullThe host being unassigned
previous_closer_namestringCached display name of the previous host
new_closer_idstringThe host being assigned
new_closer_namestringCached display name of the new host
scheduled_atstringCall's scheduled time (unchanged by reassignment)
conferencing_linkstring | nullConferencing URL on the new event
new_event_idstringThe canonical Nylas event ID after the swap
booking_link_idstring | nullOriginating scheduler
reassigned_by_user_idstringAdmin or Partner API caller who performed the swap
old_event_orphanedbooleantrue if the old event could not be cleanly canceled on the previous host's calendar

CallAnalysisWebhookData

Emitted on call.analysis_completed. Fired when the AI review of a call finishes. Carries headline scores for routing/alerting plus analysis_url for the full review — the full analysis body is intentionally not inlined (tens of KB of evolving JSON).

FieldTypeDescription
analysis_idstringAnalysis record ID
call_idstringCall that was analyzed
lead_idstring | nullAssociated lead, if known
overall_scorenumber | null0–100 overall score; null when unscorable (e.g. no-show)
closing_scorenumber | null0–100 closing score
emotional_scorenumber | null0–100 emotional score
structure_scorenumber | null0–100 structure score
buyer_certainty_indexnumber | nullBuyer certainty index
verdictstring | null"hire", "retain", or "replace"
confidencenumber | nullModel confidence 0–1
primary_objectionstring | nullPrimary objection category (e.g. "money", "timing")
offer_madeboolean | nullWhether an offer was presented on the call
analyzed_atstringWhen analysis finished (ISO 8601)
analysis_urlstringAbsolute URL of GET /v1/calls/{id}/analysis for the full review

PaymentWebhookData

Emitted on payment.succeeded, payment.failed, payment.refunded.

FieldTypeDescription
idstringPayment ID
lead_idstring | nullAssociated lead, if known
call_idstring | nullAssociated call, if the payment is tied to a deal
amountnumberAmount in the currency's smallest unit (e.g. cents for USD)
currencystringISO 4217 lowercase (usd, eur, etc.)
statusstringAura's canonical payment status (succeeded, failed, refunded, pending). Providers use different words for the same state — a Stripe Invoice says paid where a Charge says succeeded — so provider vocabulary is normalised on write. Treat succeeded as the single "money arrived" value.
payment_datestringWhen the payment was processed (ISO 8601)
created_atstringWhen the payment record was created

Conventions

Timestamps

All timestamps are ISO 8601 UTC (Z suffix):

2026-05-12T14:30:00.000Z

Null handling

Optional fields are emitted as null rather than omitted. Always treat the contract as nullable for any field marked string | null here:

const phone = data.phone ?? "(not provided)";

Forward compatibility

Aura may add new fields to any payload without a major version bump. Consumers must ignore unknown fields. Removing or renaming a documented field is a breaking change and will trigger a schema-diff review on our side before shipping.

Payload version

Every webhook envelope carries the payload contract version in version, and every Zapier payload carries the same value in aura_version. It is the version of the Aura Zapier app, so the two always match.

In October 2026 it jumped from 1.8.5 to 1.11.0. Nothing in the payload changed with that jump: the value had fallen behind the Zapier app, and now it is pinned to it. The next release, 1.12.0, adds the consent fields, 1.13.0 hides the Lead Status Changed trigger, 1.14.0 adds form_completed to lead.disqualified, 1.15.0 adds the lead.deleted event, 1.15.1 changes only how Lead Disqualified is described: it fires 3 minutes after a lead stops, not 30, 1.16.0 adds deal payments to call.closed, and 1.17.0 adds consent_by_type. Treat version as information. Don't branch on it: check for the fields you need instead.


Verifying signatures

Webhook deliveries carry a signed header X-Aura-Signature-V1 of the form:

X-Aura-Signature-V1: t=1715520600000,v1=<sha256_hex>[,v1=<previous_sig>]

The signed string is ${timestamp}.${rawBody} — not the body alone — which prevents replay attacks. Reject deliveries whose t differs from your server clock by more than 5 minutes.

During a 24-hour secret rotation window, two v1= segments are present. Accept the delivery if either signature verifies — this lets you rotate secrets with zero downtime.

The legacy bare-hex X-Aura-Signature header is still sent for backwards compatibility but offers no replay protection; new integrations should verify the V1 header only.


Next steps

On this page