Skip to content
Linq

Webhooks

ModelsExpand Collapse
MessageReceivedWebhookEvent object { id, created_at, data, type }
id: string

Delivery id (whd_…), equal to the webhook-id header.

created_at: string
formatdate-time
data: object { brand_id, chat_id, message, 3 more }
brand_id: string

The logical brand this chat is scoped under, the value to answer as. Distinct from the Apple business UUID, which is served beside it as business_id; brand_unknown:<business_id> for an unregistered business.

chat_id: string
message: object { reply, text, attachments, 4 more }
reply: object { kind, answers, items, 3 more }

The normalized interactive response when the inbound was type:interactive; null otherwise (including a customer answering a DEGRADED prompt in plain text).

kind: string

Normalized reply kind. One of app_extension, apple_pay, choices, form, invitation_response, list_picker, time_slots. Each kind carries its own shape. The six that answer a part you sent are shaped by that part’s published schema, served at GET /v1/parts/{type}; invitation_response answers an invitation rather than a part, and reports the customer’s verdict in item_id. This set describes replies classified on or after 2026-08-27. app_extension was called imessage_app before that, and a recorded event is never rewritten, so events recorded earlier keep the old spelling wherever you read them back: the event log, a chat’s own events, the transcript, and a redelivered webhook. Accept imessage_app as a historical synonym when you read history.

answers: optional array of object { field_id, item_ids, kind, 4 more }

One record per answered page, on form replies; a page whose submission carried no items emits no record. question is the page’s title, the prompt the customer answered; kind says what shape of answer it is (input, single_select, multi_select, picker, date). A page answered with one item reports field_id and value; one answered with several reports item_ids and values, aligned entry for entry. Records carry the wire’s raw fields, while the reply’s own values map (unchanged) applies a display fallback per item: its value, else its identifier, else its title. So rebuild a page’s values entry from a record by that fallback, never by joining the record’s values alone. The one leg a record cannot rebuild is the title: records carry no titles, so when an item brought neither a value nor an identifier, read that page’s answer from values, where the title already landed. Every field is present only when the submission carried its source.

field_id: optional string
item_ids: optional array of string
kind: optional string

One of input, single_select, multi_select, picker, date. Present only when the submission said which.

page_id: optional string
question: optional string
value: optional string
values: optional array of string
items: optional array of object { id, subtitle, title }

The chosen options with their display text, on list_picker replies: one entry per selected item, in the order the reply listed them. Display only: item_ids is unchanged and stays the set to match on. Present only when at least one chosen item carried display text; each entry’s title and subtitle are themselves present only when set.

id: optional string
subtitle: optional string
title: optional string
request_id: optional string

The identifier of the interactive message this reply answers: the same value the sending message’s transcript row reports as its own request_id, so a tap is matched to the exact send that asked rather than inferred from item ids (the same card sent twice shares item ids, while each send delivers under a fresh identifier). Present when the send recorded one.

timezone_offset_minutes: optional number

On form replies only: the customer’s device timezone offset at submission, verbatim from the wire. Minutes, positive west of UTC (the JavaScript getTimezoneOffset convention, so a US Central device reads +300). Present exactly when the device reported one; never fabricated. Other reply kinds never carry it: the similarly named field on a time-slot reply is an echo of the event your own send defined, not customer data.

title: optional string

The chosen option’s display text, as the customer saw it, on choices replies. Display only: match on item_id, never on this text. Present only when the device sent it, so replies recorded before it was captured have no such key.

text: string

Plain-text body; null when the inbound carried none.

attachments: optional array of object { decrypted, id, decrypted_size, 6 more }

Present when the inbound carried files: one entry per attachment, with the outcome of our decrypt-and-retain pass. Absent when the message carried none, so an ordinary text inbound’s shape is unchanged.

decrypted: boolean

Whether we could decrypt the file. false means no bytes exist to serve and error says what happened.

id: optional string

The retained file’s att_id; GET /v1/attachments/{att}/content serves the bytes and this part sends them onward. Absent when nothing was retained, and retention then says why.

decrypted_size: optional number

Plaintext size in bytes; present when decrypted.

formatint64
error: optional string

The decrypt failure, when decrypted is false.

mime_type: optional string
name: optional string

Filename as the customer sent it.

retention: optional "too_large" or "brand_unresolved" or "store_unavailable" or "disabled"

Why no id was minted for a file that DID decrypt; present exactly when that happened. too_large is yours to act on (the file exceeds what we retain); the other values are platform conditions.

One of the following:
"too_large"
"brand_unresolved"
"store_unavailable"
"disabled"
sha256: optional string

Hex SHA-256 of the plaintext; present when decrypted.

size: optional number

Transfer size in bytes, as delivered.

formatint64
group: optional string

The entry point’s groupID, same delivery and same present-only-when-set rule as intent.

intent: optional string

The entry point’s intentID, exactly as Apple delivered it. The button, URL or QR code the customer started from sets this, not the message body. It is present only when the entry point set one, so an ordinary chat’s shape is unchanged. Route your own skills on it — our brand routing matches the same representation.

raw: optional unknown

The full inbound Apple envelope, verbatim, when present. A legacy escape hatch, not a surface to automate on: everything a workflow needs is in the normalized fields (text, reply, attachments, business_id), and this key is ABSENT on ordinary deliveries unless the platform is configured to send it. Two promises hold whatever that configuration says: a delivery carrying reply_unresolved always includes raw (the payload we could not normalize), and the verbatim envelope is always readable from GET /v1/chats/{chat}/events, which is the forensic record.

reply_unresolved: optional object { reason, step }

Present only when Apple substituted an interactiveDataRef for a >10 KB reply and resolution failed permanently — reply is then null.

reason: string
step: string
owner: string

Who owned the turn when this was recorded (partner, flow, human_pending, human).

seq: number

The inbound’s seq — the events cursor, and part of the dedupe key.

formatint64
business_id: optional string

The Apple business UUID this message arrived on. Present when the inbound envelope named one, which is every ordinary delivery; when Apple sends none the key is absent, never an empty string. Use this rather than reading the envelope inside raw.

type: "message.received"
MessageSentWebhookEvent object { id, created_at, data, type }
id: string
created_at: string
formatdate-time
data: object { chat_id, message_id, seq, 2 more }
chat_id: string
message_id: string
seq: number
formatint64
group_id: optional string

The FIRST message’s id of a fan-out (a promoted URL) — absent on an ordinary single-message send.

invitation: optional object { id, reference_id, template_id }

Present only for an invitation delivery (POST /v1/invitations); reference_id is the partner’s correlation handle.

id: string
reference_id: string
template_id: string
type: "message.sent"
MessageFailedWebhookEvent object { id, created_at, data, type }
id: string
created_at: string
formatdate-time
data: object { chat_id, message_id, reason, 4 more }
chat_id: string
message_id: string
reason: "permanent" or "undelivered" or "auth_error" or 5 more

platform_error and invitation_undeliverable occur only for invitation-class sends, so those two are worth resubmitting only there.

One of the following:
"permanent"
"undelivered"
"auth_error"
"suppressed"
"consent_revoked"
"automation_paused"
"platform_error"
"invitation_undeliverable"
seq: number
formatint64
group_id: optional string

Same meaning as on message.sent — ties a fan-out member’s failure back to the one request.

last_error: optional string

Gateway error of the last attempt; rides only on a gateway-verdict failure.

last_status: optional number

Gateway HTTP status of the last attempt; rides only on a gateway-verdict failure.

type: "message.failed"
ChatClosedWebhookEvent object { id, created_at, data, type }
id: string
created_at: string
formatdate-time
data: object { chat_id, seq }
chat_id: string
seq: number
formatint64
type: "chat.closed"
ChatReopenedWebhookEvent object { id, created_at, data, type }
id: string
created_at: string
formatdate-time
data: object { chat_id, seq }
chat_id: string
seq: number
formatint64
type: "chat.reopened"
ChatOwnerChangedWebhookEvent object { id, created_at, data, type }
id: string
created_at: string
formatdate-time
data: object { chat_id, owner, seq, 2 more }
chat_id: string
owner: string

The NEW owner — partner, flow, human_pending or human. flow is a platform flow holding the turn (the survey /resolve starts, the question /clarify asks): it ends on the customer’s answer or its own timeout, and ownership then returns through this same event.

seq: number
formatint64
reason: optional string

Free-text reason when the causing transition carried one (e.g. a partner handoff).

summary: optional string

Partner-supplied summary when the causing transition carried one.

type: "chat.owner_changed"
ChatHandoffRequestedWebhookEvent object { id, created_at, data, type }
id: string
created_at: string
formatdate-time
data: object { chat_id, owner, seq, 3 more }
chat_id: string
owner: string

Always human_pending for this kind.

seq: number
formatint64
reason: optional string

Why — your own free-text reason, or trigger_word for an escalation we matched.

summary: optional string

Partner-supplied summary when the handoff carried one.

term: optional string

The exact standardized word/phrase matched; only on reason:"trigger_word".

type: "chat.handoff_requested"
ChatSendForcedWebhookEvent object { id, created_at, data, type }
id: string
created_at: string
formatdate-time
data: object { chat_id, message_id, overridden_owner, 2 more }
chat_id: string
message_id: string

Joins the event to the forced send.

overridden_owner: string

The owner as it was at commit — who was spoken over.

owner: string
seq: number
formatint64
type: "chat.send_forced"
ChatAuthenticatedWebhookEvent object { id, created_at, data, type }
id: string
created_at: string
formatdate-time
data: object { chat_id, seq, sub, 2 more }
chat_id: string
seq: number
formatint64
sub: string

The provider’s opaque subject identifier. This is the whole of what we pass through from the claims.

message_id: optional string

The id of the authenticate message this sign-in completed, equal to the send response’s message_id and the transcript business row’s message_id. A correlation id, not a claim. Absent on a sign-in whose bubble was sent before this shipped.

request_id: optional string

The requestIdentifier the authenticate message this sign-in completed was delivered under, equal to the transcript business row’s request_id. A correlation id, not a claim. Every authenticate send has minted one since the flow shipped, so it is present on a sign-in whose bubble predates this field.

type: "chat.authenticated"
ChatAuthenticationClosedWebhookEvent object { id, created_at, data, type }
id: string
created_at: string
formatdate-time
data: object { chat_id, request_id, seq, 2 more }
chat_id: string
request_id: string

The requestIdentifier the authenticate message was delivered under, equal to the transcript business row’s request_id and to chat.authenticated’s. A correlation id, not a claim; always present.

seq: number
formatint64
status: "cancel" or "failure" or "unknown" or "expired"

Why the bubble is dead. cancel: the customer dismissed it. failure: the sign-in did not complete. unknown: the device could not say, or reported a status outside this set. expired: the 30-minute sign-in window, counted from when the message was accepted, closed with no chat.authenticated and no earlier close.

One of the following:
"cancel"
"failure"
"unknown"
"expired"
message_id: optional string

The id of the authenticate message that closed, equal to the send response’s message_id and the transcript business row’s. A correlation id, not a claim. Absent on a bubble that was sent before this shipped.

type: "chat.authentication_closed"
ChatTriggerWordWebhookEvent object { id, created_at, data, type }
id: string
created_at: string
formatdate-time
data: object { action, chat_id, handled_by, 8 more }
action: "escalate" or "opt_out" or "opt_in" or 3 more
One of the following:
"escalate"
"opt_out"
"opt_in"
"menu"
"preferences"
"stop_automation"
chat_id: string
handled_by: "partner" or "platform"

Who the event asks something of. platform: informational, nothing owed. partner: yours, and the delegation fields say how much. With queued_for_human present, routing was already seized and only the acknowledgement is delegated to you, deadline ack_by. With queued_for_human ABSENT, your brand runs self-managed escalation and the whole escalation is yours: nothing was queued, and the chat moves only on your /handoff or /clarify.

One of the following:
"partner"
"platform"
owner: string
seq: number

The seq of the message.received this rides with.

formatint64
term: string

The exact word/phrase matched — the deterministic floor to tune your NLU against.

ack_by: optional string

The deadline for your acknowledgement, about 10 seconds out. Send anything before it and yours is what the customer reads; past it we send our own. It is not a routing fallback.

formatdate-time
agents_available: optional boolean

Honest reachability signal — never fabricated.

escalation_channel: optional "offered"

The escalation entered the escalation-channel flow (out-of-hours/exempt brand) — no ack is delegated and no ack_by is promised.

queued_for_human: optional boolean

Routing was secured at t=0 regardless of partner behaviour; false on the escalation-channel path. ABSENT (not false) on a self-managed brand’s escalation, and that absence is the field to branch on.

wait_estimate_seconds: optional number

Rides only when a real estimate is configured — never invented.

type: "chat.trigger_word"
ChatEscalationChannelRequestedWebhookEvent object { id, created_at, data, type }
id: string
created_at: string
formatdate-time
data: object { channel, chat_id, seq, 3 more }
channel: "phone" or "email"
One of the following:
"phone"
"email"
chat_id: string
seq: number

The seq of the customer turn that completed the request.

formatint64
email: optional string

The reply-to address — rides with channel:"email".

message: optional string

The customer’s message body — rides with channel:"email".

phone: optional string

The callback number — rides with channel:"phone".

type: "chat.escalation_channel_requested"
InvitationAcceptedWebhookEvent object { id, created_at, data, type }
id: string
created_at: string
formatdate-time
data: object { accepted_chat_id, chat_id, invitation_id, seq }
accepted_chat_id: string

The opaque chat the customer landed in — send there.

chat_id: string
invitation_id: string
seq: number
formatint64
type: "invitation.accepted"
InvitationDeclinedWebhookEvent object { id, created_at, data, type }
id: string
created_at: string
formatdate-time
data: object { chat_id, invitation_id, seq }
chat_id: string
invitation_id: string
seq: number
formatint64
type: "invitation.declined"
InvitationOptedOutWebhookEvent object { id, created_at, data, type }
id: string
created_at: string
formatdate-time
data: object { chat_id, seq }
chat_id: string
seq: number
formatint64
type: "invitation.opted_out"
InvitationBatchRecipientFailedWebhookEvent object { id, created_at, data, type }
id: string
created_at: string
formatdate-time
data: object { batch_id, brand_id, idx, 4 more }
batch_id: string

The run this recipient belongs to (invb_…).

brand_id: string
idx: number

This row’s zero-based position in the array you submitted. It is the same index each validation error names, and the key the recipients page walks.

formatint32
reason: object { code, message }

Why it will never send. The same {code, message} pair the recipients page returns for this row.

code: string
message: string
reference_id: string

Your correlation handle for this recipient, echoed back.

state: string

Always failed — the recipient state this row now holds.

to: string

The recipient string AS YOU SENT IT, not our canonical form.

type: "invitation_batch.recipient_failed"
InvitationBatchCompletedWebhookEvent object { id, created_at, data, type }
id: string
created_at: string
formatdate-time
data: object { accepted_count, batch_id, brand_id, 3 more }
accepted_count: number

recipient_count minus the rows the submit screen skipped — the same number the submit returned.

formatint32
batch_id: string
brand_id: string
counts: object { cancelled, failed, pending, 2 more }

The run’s recipients by state, all five keys present. pending is zero on a completed run by definition.

cancelled: number
formatint32
failed: number
formatint32
pending: number
formatint32
sent: number
formatint32
skipped: number
formatint32
recipient_count: number

The submitted size, frozen at creation. Always counts’ total.

formatint32
status: "completed" or "cancelled"

How the run ended.

One of the following:
"completed"
"cancelled"
type: "invitation_batch.completed"
BrandEscalationHandlingChangedWebhookEvent object { id, created_at, data, type }
id: string
created_at: string
formatdate-time
data: object { brand_id, new, old }
brand_id: string
new: "managed" or "self_managed"
One of the following:
"managed"
"self_managed"
old: "managed" or "self_managed"
One of the following:
"managed"
"self_managed"
type: "brand.escalation_handling_changed"
ChatAutomationPausedWebhookEvent object { id, created_at, data, type }
id: string
created_at: string
formatdate-time
data: object { chat_id, seq }
chat_id: string
seq: number

The causing inbound message’s seq.

formatint64
type: "chat.automation_paused"
ChatAutomationResumedWebhookEvent object { id, created_at, data, type }
id: string
created_at: string
formatdate-time
data: object { chat_id, seq }
chat_id: string
seq: number

The causing inbound message’s seq.

formatint64
type: "chat.automation_resumed"
ReactionAddedWebhookEvent object { id, created_at, data, type }
id: string
created_at: string
formatdate-time
data: object { brand_id, chat_id, correlation, 12 more }
brand_id: string
chat_id: string
correlation: "inferred" or "ambiguous" or "unmatched" or "unavailable"

HOW the target was reached, and the same value GET /v1/chats/{chat}/transcript serves for this reaction. inferred means exactly one message in the recent window carried that text; ambiguous means several did and candidate_count says how many; unmatched means none did; unavailable means Apple named no target. There is no exact, because no reaction on this wire carries an id.

One of the following:
"inferred"
"ambiguous"
"unmatched"
"unavailable"
kind: "like" or "love" or "dislike" or 5 more

What the customer did. The same closed set the transcript’s reaction.kind uses, spelled the same way.

One of the following:
"like"
"love"
"dislike"
"laugh"
"emphasize"
"question"
"sticker"
"emoji"
owner: string

Who owned the turn when this was recorded (partner, flow, human_pending, human). It rides every envelope so an orchestrator branches structurally instead of guessing.

seq: number

This reaction’s own seq in the chat.

formatint64
source_seq: number

The seq of the customer message that CARRIED the reaction. It is not the message reacted to; that is target_seq, and it is an inference.

formatint64
attachment_id: optional string

The sticker’s image, when kind is sticker and we retained it. Pass it to GET /v1/attachments/{att}/content. Absent when the image was not retained, or when more than one file arrived and the wire does not say which is the sticker. Do not assume a format; read the response’s own content type. A present id is NOT a promise of bytes, exactly as for att_id. Retained attachments are swept after their published TTL and the event is immutable, so an old reaction still names the id it was minted with and the content read answers 404.

candidate_count: optional number

How many messages carried that exact text, on ambiguous. No winner is picked for you.

formatint32
emoji: optional string

The character the customer picked, present only when kind is emoji. Served verbatim.

generic: optional boolean

Apple named no target at all, which is what a reaction to a non-text message looks like. correlation is then unavailable.

quoted_text: optional string

The original message’s text as Apple quoted it back — the only handle this wire offers, and what the correlation was made against.

target_msg_id: optional string

The message reacted to, on inferred.

target_seq: optional number

That message’s seq, on inferred.

formatint64
target_side: optional "business" or "customer"

Whose message was reacted to, on inferred. A customer can react to their own, so it is not derivable from the reaction being a customer event.

One of the following:
"business"
"customer"
type: "reaction.added"
ReactionRemovedWebhookEvent object { id, created_at, data, type }
id: string
created_at: string
formatdate-time
data: object { brand_id, chat_id, correlation, 11 more }
brand_id: string
chat_id: string
correlation: "inferred" or "ambiguous" or "unmatched" or "unavailable"

HOW the target was reached, and the same value GET /v1/chats/{chat}/transcript serves for this reaction. inferred means exactly one message in the recent window carried that text; ambiguous means several did and candidate_count says how many; unmatched means none did; unavailable means Apple named no target. There is no exact, because no reaction on this wire carries an id.

One of the following:
"inferred"
"ambiguous"
"unmatched"
"unavailable"
kind: "like" or "love" or "dislike" or 5 more

What the customer did. The same closed set the transcript’s reaction.kind uses, spelled the same way.

One of the following:
"like"
"love"
"dislike"
"laugh"
"emphasize"
"question"
"sticker"
"emoji"
owner: string

Who owned the turn when this was recorded (partner, flow, human_pending, human). It rides every envelope so an orchestrator branches structurally instead of guessing.

seq: number

This reaction’s own seq in the chat.

formatint64
source_seq: number

The seq of the customer message that CARRIED the reaction. It is not the message reacted to; that is target_seq, and it is an inference.

formatint64
candidate_count: optional number

How many messages carried that exact text, on ambiguous. No winner is picked for you.

formatint32
emoji: optional string

The character the customer picked, present only when kind is emoji. Served verbatim.

generic: optional boolean

Apple named no target at all, which is what a reaction to a non-text message looks like. correlation is then unavailable.

quoted_text: optional string

The original message’s text as Apple quoted it back — the only handle this wire offers, and what the correlation was made against.

target_msg_id: optional string

The message reacted to, on inferred.

target_seq: optional number

That message’s seq, on inferred.

formatint64
target_side: optional "business" or "customer"

Whose message was reacted to, on inferred. A customer can react to their own, so it is not derivable from the reaction being a customer event.

One of the following:
"business"
"customer"
type: "reaction.removed"
ChoiceReceivedWebhookEvent object { id, created_at, data, type }
id: string
created_at: string
formatdate-time
data: object { brand_id, chat_id, item_id, 5 more }
brand_id: string
chat_id: string
item_id: string

The chosen option’s id, your own id from the sent part. The only match key.

owner: string

Who owned the turn when this was recorded (partner, flow, human_pending, human).

seq: number

The same seq as the inbound event that produced it, the join to GET /v1/chats/{chat}/events.

formatint64
business_id: optional string

The Apple business UUID the chat arrived on; present when the inbound named one.

request_id: optional string

The identifier of the interactive message this reply answers; present when the send recorded one.

title: optional string

The chosen option’s display text as the reply carried it; present when it did. Display passthrough, never a match key.

type: "choice.received"
PickerReceivedWebhookEvent object { id, created_at, data, type }
id: string
created_at: string
formatdate-time
data: object { brand_id, chat_id, items, 4 more }
brand_id: string
chat_id: string
items: array of object { item_id, subtitle, title }

The chosen entries, in the reply’s order.

item_id: string

Your own item id from the sent part. The only match key.

subtitle: optional string

The item’s subtitle; present when the reply carried it.

title: optional string

The item’s display text; present when the reply carried it.

owner: string

Who owned the turn when this was recorded (partner, flow, human_pending, human).

seq: number

The same seq as the inbound event that produced it, the join to GET /v1/chats/{chat}/events.

formatint64
business_id: optional string

The Apple business UUID the chat arrived on; present when the inbound named one.

request_id: optional string

The identifier of the interactive message this reply answers; present when the send recorded one.

type: "picker.received"
TimeslotReceivedWebhookEvent object { id, created_at, data, type }
id: string
created_at: string
formatdate-time
data: object { brand_id, chat_id, owner, 5 more }
brand_id: string
chat_id: string
owner: string

Who owned the turn when this was recorded (partner, flow, human_pending, human).

seq: number

The same seq as the inbound event that produced it, the join to GET /v1/chats/{chat}/events.

formatint64
start: string

The chosen slot’s start time, carried as the device sent it.

business_id: optional string

The Apple business UUID the chat arrived on; present when the inbound named one.

request_id: optional string

The identifier of the interactive message this reply answers; present when the send recorded one.

slot_id: optional string

The chosen slot’s identifier from your sent part; present when the reply carried it.

type: "timeslot.received"
FormCompletedWebhookEvent object { id, created_at, data, type }
id: string
created_at: string
formatdate-time
data: object { brand_id, chat_id, owner, 11 more }
brand_id: string
chat_id: string
owner: string

Who owned the turn when this was recorded (partner, flow, human_pending, human).

seq: number

The seq of the event that concluded the form, the join to GET /v1/chats/{chat}/events.

formatint64
transport: "native" or "paged"

How the form reached the customer. native is a single-page card submission; paged is the degraded multi-page collection.

One of the following:
"native"
"paged"
values: map[string]

The flat answers, keyed by your original page ids, multi-select values comma-joined. Byte-congruent with the message-level reply’s values and with form.response.

answers: optional array of object { kind, page_id, field_id, 4 more }

The typed per-field record; present when captured at ingest. One entry per answered field, preserving page grouping and field kind.

kind: "input" or "single_select" or "multi_select" or 2 more

The field kind, normalized.

One of the following:
"input"
"single_select"
"multi_select"
"picker"
"date"
page_id: string

Your original page id.

field_id: optional string

The field’s own id, when the page distinguishes one.

item_ids: optional array of string

The selected item ids for a choice field.

question: optional string

The page’s own prompt text, so the answer reads with its meaning; present when the submission carried it.

value: optional string

The answer for a single-valued field.

values: optional array of string

The answers for a multi-valued field.

business_id: optional string

The Apple business UUID the chat arrived on; present when the inbound named one (native transport only).

closed_at: optional string

Paged transport; when the collection concluded.

formatdate-time
group_id: optional string

Paged transport; the correlation id tying the record to the page messages that asked.

item_ids: optional array of string

Every selected item id across the form’s choice pages; present when any page was a choice and the ids were captured.

opened_at: optional string

Paged transport; when the collection opened.

formatdate-time
request_id: optional string

Native transport; the identifier of the form message this submission answers, present when the send recorded one.

timezone_offset_minutes: optional number

The customer device’s UTC offset in minutes, positive west of UTC, exactly as the submission carried it; present when it did (native transport only).

type: "form.completed"
ApplePayReceivedWebhookEvent object { id, created_at, data, type }
id: string
created_at: string
formatdate-time
data: object { brand_id, chat_id, owner, 4 more }
brand_id: string
chat_id: string
owner: string

Who owned the turn when this was recorded (partner, flow, human_pending, human).

payment_state: string

The completion message’s state, advisory only. NON-AUTHORITATIVE; reconcile against your own payment record, never this field.

seq: number

The same seq as the inbound event that produced it, the join to GET /v1/chats/{chat}/events.

formatint64
business_id: optional string

The Apple business UUID the chat arrived on; present when the inbound named one.

request_id: optional string

The identifier of the payment message this completion answers; present when the send recorded one.

type: "apple_pay.received"
AppExtensionReceivedWebhookEvent object { id, created_at, data, type }
id: string
created_at: string
formatdate-time
data: object { bid, brand_id, chat_id, 8 more }
bid: string

The extension bundle id the reply arrived under.

brand_id: string
chat_id: string
owner: string

Who owned the turn when this was recorded (partner, flow, human_pending, human).

seq: number

The same seq as the inbound event that produced it, the join to GET /v1/chats/{chat}/events.

formatint64
business_id: optional string

The Apple business UUID the chat arrived on; present when the inbound named one.

data: optional unknown

The raw dictionary your extension sent, verbatim; present when one exists.

request_id: optional string

The identifier of the message this reply answers; present when the reply echoed one.

session_id: optional string

The extension’s session identifier; present when the reply carried one.

url: optional string

The URL-encoded state your extension sent; present when the reply carried one.

url_params: optional array of object { name, value }

The url query string parsed to pairs, in order, duplicates preserved; present when the URL carried a query.

name: string
value: string
type: "app_extension.received"