This is the reference for developers building against Booth.Events webhooks. If you are setting webhooks up for the first time, or connecting IFTTT / Zapier / Dropbox, start with How to Use Webhooks to integrate with services like IFTTT/Zapier/Dropbox and come back here for the payload detail.
Webhooks are advanced functionality. Booth.Events does not provide setup support for integrations with third-party services.
Requirements
Pro+ subscription.
An endpoint that accepts
HTTP POSToverhttpsand returns quickly.
Both URLs are set on the Account page, under Sharing Settings → Webhook. They are account-wide: every event (gallery) on the account uses them.
Two URLs, two events
Setting |
| Fires |
Upload webhook URL |
| Once for every media file created in a gallery. A four-shot session with a template produces five deliveries (four originals + one templated photo). |
Share webhook URL |
| A guest shares a whole session by email or SMS. |
Branch on the event field rather than on payload shape — it is what lets us add an event to an existing URL without breaking your integration.
What each webhook is best used for
media.uploaded
Reacting to each new file the moment it exists — print queues, social walls, live slideshows, AI post-processing.
Pairing with a hot folder. If you have Dropbox / Google Drive / SmugMug connected, the same file lands in your storage under the exact string in
filename. Match on that to attach metadata to a file your own system picked up from the folder.Splitting originals from templated photos and video without guessing — use
mediaType.
session.sent
Capturing guest contact details at the moment a guest chooses to share.
Getting the complete set of survey answers for a session (see Survey timing).
Getting downloadable URLs for every file in the session in one payload, with no follow-up API call.
What these webhooks cannot do
Read this section before you design around them.
They are not a cloud-storage notification. If you have Dropbox / Google Drive / SmugMug connected, that upload runs after the webhook and has no webhook of its own.
media.uploadedtells you a file exists in Booth.Events, not that it has appeared in your storage folder. Watch the folder itself and join onfilename.There are no retries. Delivery is fire-and-forget. If your endpoint is down, times out, or returns an error, that delivery is gone — there is no queue, no backoff and no delivery log. Accept fast, process asynchronously on your side.
Delivery is at-least-once, not exactly-once. If the iPad retries an upload,
media.uploadedfires again for the same media. De-duplicate ondeliveryId, and checkisNewMedia—falsemeans you have seen this file before.There is no ordering guarantee. The files of one session are uploaded in parallel; the templated photo can arrive before the originals it was built from, and its
filenamecounter can be lower even though it was created later. The order is not even stable within one session — two captures in the same session can number themselves in opposite orders. Do not infer capture order from filename.You cannot subscribe per event (gallery), or to a subset of media types. The URLs are account-wide and receive everything. Filter on
galleryId/mediaTypeat your end.media.uploadedis not a settled record of survey answers. See Survey timing.Preview mode does not fire webhooks. You must launch the event on the iPad.
You must have Shared.Gallery uploads turned on.
Delivery mechanics
HTTP POST,Content-Type: application/json, UTF-8. No custom headers.Every documented key is always present. A value that does not apply is sent as
null— includingdataCollectionResponseandsurveywhen the guest answered nothing. Treat any key you do not recognise as something we added after you integrated, and ignore it.All media URLs (
url,urlThumb,fullUrl,thumbUrl) are signed and expire about 7 days after issue. Download promptly; do not store them as permanent links. The signature query string is roughly 500 characters — the examples below shorten it for readability.The same photo reaches you as two different URL strings. The two events sign their URLs with different internal identities, so the
urlonmedia.uploadedand thefullUrlonsession.sentpoint at the same file but do not match character for character. Both work. De-duplicate onmediaId, never on the URL.All dates are ISO 8601 strings.
Common envelope
These three keys are on every payload.
Key | Type | Notes |
| string |
|
| string | De-duplication key. For |
| string | Payload version, e.g. |
Event: media.uploaded
Fields
contentType(string) — e.g.image/jpeg,image/gif,video/mp4.isCollage(boolean) —truewhen a template was applied to produce this file.mediaType(string) —original,templatedorvideo. This is the same classifier we use to file media into cloud-storage sub-folders, so it always agrees with where the file lands. GIFs and boomerangs count asvideoeven though theircontentTypeisimage/gifand theirisCollageisfalse— which is why you should readmediaTyperather than deriving the classification yourself.width,height(number) — pixels.kBytes,kBytesThumb(number) — file and thumbnail size in kilobytes.url,urlThumb(string) — signed download URLs, valid about 7 days.filename(string) — the file name, identical to the name this file takes in your connected Dropbox / Google Drive / SmugMug folder.mediaId(string) — unique id of this media. Use it to join with the share webhook and with our API.tenantId(string) — your account id.deviceId(string) — identifier of the iPad that captured this file. Useful with multiple booths at one event.nullfor uploads not made by an iPad.templateId(string) — the template in use when this file was captured. Present on originals as well as templated photos, so it is not a substitute formediaType.isNewMedia(boolean) —falsemeans this is a re-delivery for a file you have already been told about.captureType(string) — e.g.photo,gif,video,aiPhoto,aiCustomPrompt.shutterDate(string) — when the photo or video was taken.eventId(string) — the Booth.Events event id.eventIdUser(string) — your own id for this event if you set one, otherwisenull. This is the field to correlate with your booking system.galleryId,galleryName,gallerySlug,galleryDate— the gallery this file belongs to.galleryPageUrl,mediaPageUrl,sessionPageUrl(string) — public pages, on your custom domain when you have one configured.sessionId(string),sessionIndex(number) — the guest session this file belongs to.sessionIndexincreases with upload order, not capture order.dataCollectionResponse(object) — raw survey answers. See dataCollectionResponse.nullwhen the guest answered nothing.survey(object) — the same answers, normalised. See survey.nullwhen the guest answered nothing.value1,value2,value3— aliases for IFTTT's "Receive a web request" trigger: the download URL, the shutter date, and the gallery name.
Full example
A templated photo from a session where the guest answered a pre-session survey, a post-capture question, and template questions.
{
"event": "media.uploaded",
"deliveryId": "6jaKikA8u7w1tpN7RbBU",
"apiVersion": "2026-08-06",
"contentType": "image/jpeg",
"isCollage": true,
"width": 1080,
"height": 1920,
"kBytes": 223,
"kBytesThumb": 43,
"url": "https://solo-s.b-cdn.net/sharedgallery.appspot.com/acmephotobooth/AXKxTSIGgBPvbP1PUnva/0095-Summer_Party_2026-d96-template.jpg?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=firebase-adminsdk-9elxx%40sharedgallery.iam.gserviceaccount.com%2F20260710%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20260710T182041Z&X-Goog-Expires=604000&X-Goog-SignedHeaders=host&X-Goog-Signature=36a68d5113b444620f5e78d8ee96f72b8088826dd22a2b52065ffeff1b6eaa55a450fefbb28986a8cb4e496d94bc5cc6",
"urlThumb": "https://solo-s.b-cdn.net/sharedgallery.appspot.com/acmephotobooth/AXKxTSIGgBPvbP1PUnva/0095-Summer_Party_2026-d96-template_genThumb.jpg?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=firebase-adminsdk-9elxx%40sharedgallery.iam.gserviceaccount.com%2F20260710%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20260710T182041Z&X-Goog-Expires=604000&X-Goog-SignedHeaders=host&X-Goog-Signature=9f441b36c34c8ed9c0be7d4f7c162d71ae4037f6ee81a1d6e67e9c496ab23bb2c4c800615bf7cb8261e3f0f9d6ed6102",
"dataCollectionResponse": {
"createdDate": "2026-07-10T14:19:44-04:00",
"responses": [
{
"capturedAt": "2026-07-10T18:19:44Z",
"stage": "preSession",
"fieldId": "fld_01KT96FSEFBMRS83EM9ZCP8QV0",
"question": {
"fieldId": "fld_01KT96FSEFBMRS83EM9ZCP8QV0",
"optional": false,
"text": "Your name",
"type": "string",
"name": "full_name"
},
"answer": "Jamie Rivera",
"source": {
"kind": "event",
"id": "40YMtavXykrjrorYkW8I"
}
},
{
"capturedAt": "2026-07-10T18:19:44Z",
"stage": "preSession",
"fieldId": "fld_01KQQRB59TZSWDCFFM0HVGCQZQ",
"question": {
"text": "Phone number",
"fieldId": "fld_01KQQRB59TZSWDCFFM0HVGCQZQ",
"name": "phone",
"type": "phone_number",
"optional": false
},
"answer": "+15551234567",
"source": {
"kind": "event",
"id": "40YMtavXykrjrorYkW8I"
}
},
{
"capturedAt": "2026-07-10T18:19:44Z",
"stage": "preSession",
"fieldId": "fld_01KQQRBB92AVT5SA6JSY0XZVBB",
"question": {
"text": "I agree to the terms and conditions",
"fieldId": "fld_01KQQRBB92AVT5SA6JSY0XZVBB",
"name": "consent",
"type": "checkbox",
"optional": false
},
"answer": "checked",
"source": {
"kind": "event",
"id": "40YMtavXykrjrorYkW8I"
}
},
{
"capturedAt": "2026-07-10T18:19:44Z",
"stage": "preSession",
"fieldId": "fld_01KQQRB8C4Y83Q9R7VDNGHA8D8",
"question": {
"type": "date",
"optional": false,
"name": "date",
"fieldId": "fld_01KQQRB8C4Y83Q9R7VDNGHA8D8",
"text": "Wedding date"
},
"answer": "2027-05-03T16:13:08-06:00",
"source": {
"kind": "event",
"id": "40YMtavXykrjrorYkW8I"
}
},
{
"capturedAt": "2026-07-10T18:19:44Z",
"stage": "preSession",
"fieldId": "fld_01KQQRBECNJYVKVCE5YS7XBD2D",
"question": {
"answers": [
"1",
"2"
],
"fieldId": "fld_01KQQRBECNJYVKVCE5YS7XBD2D",
"text": "Choose one",
"optional": false,
"type": "segment",
"name": "choice"
},
"answer": "1",
"source": {
"kind": "event",
"id": "40YMtavXykrjrorYkW8I"
}
},
{
"capturedAt": "2026-07-10T18:20:34Z",
"stage": "dynamicElements",
"fieldId": "fld_01KT95YP8MZAAKW1MZFGWKW7VS",
"question": {
"name": "Card Type",
"fieldId": "fld_01KT95YP8MZAAKW1MZFGWKW7VS",
"text": "Card Type",
"optional": false,
"type": "multi_select"
},
"answer": "ch_01KT95Z76RM2P1HJB0BEDJJYEK",
"answerLabel": "Enchantment",
"source": {
"kind": "template",
"id": "pfGwTAG6AnoXc98e88ra"
}
},
{
"capturedAt": "2026-07-10T18:20:34Z",
"stage": "dynamicElements",
"fieldId": "fld_01KT95ZT2F9T1QRH5NJME361RP",
"question": {
"type": "image_multi_select",
"fieldId": "fld_01KT95ZT2F9T1QRH5NJME361RP",
"name": "Color",
"optional": false,
"text": "Color"
},
"answer": "ch_01KT9T3TXRYYJNWQ7RV2N6RJTP",
"answerLabel": "Green",
"source": {
"kind": "template",
"id": "pfGwTAG6AnoXc98e88ra"
}
},
{
"capturedAt": "2026-07-10T18:20:41Z",
"stage": "postCapture",
"fieldId": "fld_01KT96G05WZNJ7SYBTW3B2CYYX",
"question": {
"name": "email",
"text": "Your email",
"type": "email",
"fieldId": "fld_01KT96G05WZNJ7SYBTW3B2CYYX",
"optional": false
},
"answer": "[email protected]",
"source": {
"kind": "event",
"id": "40YMtavXykrjrorYkW8I"
}
}
]
},
"survey": {
"stages": [
"preSession",
"dynamicElements",
"postCapture"
],
"answers": [
{
"fieldId": "fld_01KT96FSEFBMRS83EM9ZCP8QV0",
"question": "Your name",
"type": "string",
"stage": "preSession",
"answer": "Jamie Rivera",
"answerText": "Jamie Rivera",
"capturedAt": "2026-07-10T18:19:44Z",
"source": {
"kind": "event",
"id": "40YMtavXykrjrorYkW8I"
}
},
{
"fieldId": "fld_01KQQRB59TZSWDCFFM0HVGCQZQ",
"question": "Phone number",
"type": "phone_number",
"stage": "preSession",
"answer": "+15551234567",
"answerText": "+15551234567",
"capturedAt": "2026-07-10T18:19:44Z",
"source": {
"kind": "event",
"id": "40YMtavXykrjrorYkW8I"
}
},
{
"fieldId": "fld_01KQQRBB92AVT5SA6JSY0XZVBB",
"question": "I agree to the terms and conditions",
"type": "checkbox",
"stage": "preSession",
"answer": "checked",
"answerText": "checked",
"capturedAt": "2026-07-10T18:19:44Z",
"source": {
"kind": "event",
"id": "40YMtavXykrjrorYkW8I"
}
},
{
"fieldId": "fld_01KQQRB8C4Y83Q9R7VDNGHA8D8",
"question": "Wedding date",
"type": "date",
"stage": "preSession",
"answer": "2027-05-03T16:13:08-06:00",
"answerText": "2027-05-03T16:13:08-06:00",
"capturedAt": "2026-07-10T18:19:44Z",
"source": {
"kind": "event",
"id": "40YMtavXykrjrorYkW8I"
}
},
{
"fieldId": "fld_01KQQRBECNJYVKVCE5YS7XBD2D",
"question": "Choose one",
"type": "segment",
"stage": "preSession",
"answer": "1",
"answerText": "1",
"capturedAt": "2026-07-10T18:19:44Z",
"source": {
"kind": "event",
"id": "40YMtavXykrjrorYkW8I"
}
},
{
"fieldId": "fld_01KT95YP8MZAAKW1MZFGWKW7VS",
"question": "Card Type",
"type": "multi_select",
"stage": "dynamicElements",
"answer": "ch_01KT95Z76RM2P1HJB0BEDJJYEK",
"answerText": "Enchantment",
"capturedAt": "2026-07-10T18:20:34Z",
"source": {
"kind": "template",
"id": "pfGwTAG6AnoXc98e88ra"
}
},
{
"fieldId": "fld_01KT95ZT2F9T1QRH5NJME361RP",
"question": "Color",
"type": "image_multi_select",
"stage": "dynamicElements",
"answer": "ch_01KT9T3TXRYYJNWQ7RV2N6RJTP",
"answerText": "Green",
"capturedAt": "2026-07-10T18:20:34Z",
"source": {
"kind": "template",
"id": "pfGwTAG6AnoXc98e88ra"
}
},
{
"fieldId": "fld_01KT96G05WZNJ7SYBTW3B2CYYX",
"question": "Your email",
"type": "email",
"stage": "postCapture",
"answer": "[email protected]",
"answerText": "[email protected]",
"capturedAt": "2026-07-10T18:20:41Z",
"source": {
"kind": "event",
"id": "40YMtavXykrjrorYkW8I"
}
}
]
},
"eventId": "40YMtavXykrjrorYkW8I",
"eventIdUser": "BOOKING-48812",
"shutterDate": "2026-07-10T18:20:08Z",
"captureType": "photo",
"galleryId": "AXKxTSIGgBPvbP1PUnva",
"galleryName": "Summer Party 2026",
"gallerySlug": "summer-party-2026",
"galleryDate": "2026-07-10T16:00:00.000Z",
"galleryPageUrl": "https://shared.gallery/of/acmephotobooth/summer-party-2026",
"mediaPageUrl": "https://shared.gallery/of/acmephotobooth/summer-party-2026/m/6jaKikA8u7w1tpN7RbBU",
"sessionPageUrl": "https://shared.gallery/of/acmephotobooth/summer-party-2026/s/swW1uAfqbmVTRTysaBSj",
"value1": "https://solo-s.b-cdn.net/sharedgallery.appspot.com/acmephotobooth/AXKxTSIGgBPvbP1PUnva/0095-Summer_Party_2026-d96-template.jpg?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=firebase-adminsdk-9elxx%40sharedgallery.iam.gserviceaccount.com%2F20260710%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20260710T182041Z&X-Goog-Expires=604000&X-Goog-SignedHeaders=host&X-Goog-Signature=36a68d5113b444620f5e78d8ee96f72b8088826dd22a2b52065ffeff1b6eaa55a450fefbb28986a8cb4e496d94bc5cc6",
"value2": "2026-07-10T18:20:08Z",
"value3": "Summer Party 2026",
"sessionId": "swW1uAfqbmVTRTysaBSj",
"sessionIndex": 37,
"mediaId": "6jaKikA8u7w1tpN7RbBU",
"tenantId": "acmephotobooth",
"filename": "0095-Summer_Party_2026-d96-template.jpg",
"mediaType": "templated",
"deviceId": "4B1E9C77-3A2D-4F58-9E10-6C7A2D3F81B4",
"templateId": "pfGwTAG6AnoXc98e88ra",
"isNewMedia": true
}
Event: session.sent
Fires each time a guest enters an email address or phone number on the share screen. A guest can share more than once, so you can receive several deliveries for one session. The email and phoneNumber arrays accumulate: the newest delivery for a session contains everything entered so far. Key on sessionId to overwrite rather than append.
Fields
createdDate(string) — when the session was created, which is the capture time of the first file that finished uploading. On a multi-photo session that is not necessarily the earliest photo, so it can be later than acreatedDateinsidemediaObjects. For "when was this taken", read the media entries.email(array of strings, ornull) — every email address entered on the share screen for this session so far.phoneNumber(array of strings, ornull) — every phone number entered on the share screen, in E.164 format.Note: addresses and numbers a guest gave through the Data Collection / survey feature are not in these two arrays. Read them from
surveyordataCollectionResponse.sessionId(string),sessionIndex(number).media(array of strings) — the media ids in this session.mediaObjects(array of objects) — the full media records, including download URLs. See The mediaObjects entry.collageMediaId(string) — which entry inmediaObjectsis the templated output, ornull.tenantId,galleryId,galleryName,gallerySlug,galleryDate,galleryPageUrl,sessionPageUrl— as onmedia.uploaded.dataCollectionResponse,survey— the complete answers for the session.value1,value2,value3— IFTTT aliases for the session URL, the email array and the phone array.
Full example
Two files in the session — one original, one templated. dataCollectionResponse and survey are identical in structure to the media.uploaded example above and are shortened here to one answer so the media detail stays readable.
{
"event": "session.sent",
"deliveryId": "swW1uAfqbmVTRTysaBSj:1783103046512",
"apiVersion": "2026-08-06",
"createdDate": "2026-07-10T18:12:58.134Z",
"dataCollectionResponse": {
"createdDate": "2026-07-10T14:19:44-04:00",
"responses": [
{
"capturedAt": "2026-07-10T18:20:41Z",
"stage": "postCapture",
"fieldId": "fld_01KT96G05WZNJ7SYBTW3B2CYYX",
"question": {
"name": "email",
"text": "Your email",
"type": "email",
"fieldId": "fld_01KT96G05WZNJ7SYBTW3B2CYYX",
"optional": false
},
"answer": "[email protected]",
"source": {
"kind": "event",
"id": "40YMtavXykrjrorYkW8I"
}
}
]
},
"survey": {
"stages": [
"postCapture"
],
"answers": [
{
"fieldId": "fld_01KT96G05WZNJ7SYBTW3B2CYYX",
"question": "Your email",
"type": "email",
"stage": "postCapture",
"answer": "[email protected]",
"answerText": "[email protected]",
"capturedAt": "2026-07-10T18:20:41Z",
"source": {
"kind": "event",
"id": "40YMtavXykrjrorYkW8I"
}
}
]
},
"phoneNumber": [
"+15551234567"
],
"email": [
"[email protected]"
],
"galleryId": "AXKxTSIGgBPvbP1PUnva",
"galleryName": "Summer Party 2026",
"gallerySlug": "summer-party-2026",
"galleryDate": "2026-07-10T16:00:00.000Z",
"galleryPageUrl": "https://shared.gallery/of/acmephotobooth/summer-party-2026",
"sessionPageUrl": "https://shared.gallery/of/acmephotobooth/summer-party-2026/s/swW1uAfqbmVTRTysaBSj",
"sessionId": "swW1uAfqbmVTRTysaBSj",
"sessionIndex": 37,
"media": [
"h19OGnrjVh4sUpsBDe7m",
"6jaKikA8u7w1tpN7RbBU"
],
"mediaObjects": [
{
"id": "h19OGnrjVh4sUpsBDe7m",
"createdDate": "2026-07-10T18:12:14Z",
"contentType": "image/jpeg",
"path": "acmephotobooth/AXKxTSIGgBPvbP1PUnva/0092-Summer_Party_2026-d96.jpg",
"pathThumb": "acmephotobooth/AXKxTSIGgBPvbP1PUnva/0092-Summer_Party_2026-d96_genThumb.jpg",
"origPath": "acmephotobooth/AXKxTSIGgBPvbP1PUnva/CFCB0C7D-32B8-4024-B909-836AE29E030F_1.jpg",
"filename": "0092-Summer_Party_2026-d96.jpg",
"kBytes": 1714,
"kBytesThumb": 117,
"width": 3984,
"height": 2656,
"isCollage": false,
"captureType": "photo",
"templateId": "pfGwTAG6AnoXc98e88ra",
"blurData": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAIAAAA7ljmRAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAMElEQVR4nGNYMjHPyVp7Qme5ihQzw74NUwqz4j7//2tqrMbQVV+wOTUyLymYgYkBADRkDuQSSfWuAAAAAElFTkSuQmCC",
"fullUrl": "https://solo-s.b-cdn.net/sharedgallery.appspot.com/acmephotobooth/AXKxTSIGgBPvbP1PUnva/0092-Summer_Party_2026-d96.jpg?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=firebase-adminsdk-9elxx%40sharedgallery.iam.gserviceaccount.com%2F20260710%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20260710T181300Z&X-Goog-Expires=604000&X-Goog-SignedHeaders=host&X-Goog-Signature=fe37d0cb9cfe7ab3cda70bb4c58f82fb9ba30c1ad4a377a0e035260aa7d28704d3ad141fde510e9ddef2040756152c3f",
"thumbUrl": "https://solo-s.b-cdn.net/sharedgallery.appspot.com/acmephotobooth/AXKxTSIGgBPvbP1PUnva/0092-Summer_Party_2026-d96_genThumb.jpg?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=firebase-adminsdk-9elxx%40sharedgallery.iam.gserviceaccount.com%2F20260710%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20260710T181300Z&X-Goog-Expires=604000&X-Goog-SignedHeaders=host&X-Goog-Signature=5a137d9a87bd414e2b44e8b1612634e2f1fa7cc3f1d917a43b9a332fc2d637ebbd3aef6de192bbcaad08320a3c6c6c83",
"mediaType": "original"
},
{
"id": "6jaKikA8u7w1tpN7RbBU",
"createdDate": "2026-07-10T18:20:08Z",
"contentType": "image/jpeg",
"path": "acmephotobooth/AXKxTSIGgBPvbP1PUnva/0095-Summer_Party_2026-d96-template.jpg",
"pathThumb": "acmephotobooth/AXKxTSIGgBPvbP1PUnva/0095-Summer_Party_2026-d96-template_genThumb.jpg",
"origPath": "acmephotobooth/AXKxTSIGgBPvbP1PUnva/CFCB0C7D-32B8-4024-B909-836AE29E030F_2.jpg",
"filename": "0095-Summer_Party_2026-d96-template.jpg",
"kBytes": 223,
"kBytesThumb": 43,
"width": 1080,
"height": 1920,
"isCollage": true,
"captureType": "photo",
"templateId": "pfGwTAG6AnoXc98e88ra",
"blurData": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAAECAIAAAArjXluAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAJElEQVR4nGPwDPV0tLJhyMgo+P7/PwMbBwcTMwuDkLCQsLAwAHHiBmN2ukkjAAAAAElFTkSuQmCC",
"fullUrl": "https://solo-s.b-cdn.net/sharedgallery.appspot.com/acmephotobooth/AXKxTSIGgBPvbP1PUnva/0095-Summer_Party_2026-d96-template.jpg?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=firebase-adminsdk-9elxx%40sharedgallery.iam.gserviceaccount.com%2F20260710%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20260710T182041Z&X-Goog-Expires=604000&X-Goog-SignedHeaders=host&X-Goog-Signature=36a68d5113b444620f5e78d8ee96f72b8088826dd22a2b52065ffeff1b6eaa55a450fefbb28986a8cb4e496d94bc5cc6",
"thumbUrl": "https://solo-s.b-cdn.net/sharedgallery.appspot.com/acmephotobooth/AXKxTSIGgBPvbP1PUnva/0095-Summer_Party_2026-d96-template_genThumb.jpg?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=firebase-adminsdk-9elxx%40sharedgallery.iam.gserviceaccount.com%2F20260710%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20260710T182041Z&X-Goog-Expires=604000&X-Goog-SignedHeaders=host&X-Goog-Signature=9f441b36c34c8ed9c0be7d4f7c162d71ae4037f6ee81a1d6e67e9c496ab23bb2c4c800615bf7cb8261e3f0f9d6ed6102",
"mediaType": "templated"
}
],
"value1": "https://shared.gallery/of/acmephotobooth/summer-party-2026/s/swW1uAfqbmVTRTysaBSj",
"value2": [
"[email protected]"
],
"value3": [
"+15551234567"
],
"tenantId": "acmephotobooth",
"collageMediaId": "6jaKikA8u7w1tpN7RbBU"
}
The mediaObjects entry
Every key you can expect on an entry in the mediaObjects array of session.sent.
id(string) — the media id, the same value thatmedia.uploadedsends asmediaId. This is the key to join the two events on.createdDate(string) — capture time.contentType(string).mediaType(string) —original,templatedorvideo, classified exactly as onmedia.uploaded. Read this rather than deriving it fromisCollage, which would misfile GIFs and boomerangs.filename(string) — the same name the file takes in a connected cloud-storage folder.path,pathThumb,origPath(string) — internal storage paths. Not downloadable. UsefullUrl/thumbUrl.fullUrl,thumbUrl(string) — signed download URLs, valid about 7 days. These will not be character-identical to theurl/urlThumbthe same file arrived with onmedia.uploaded, even though they point at the same object.kBytes,kBytesThumb(number).width,height(number) — pixels.isCollage(boolean) — a template was applied.captureType(string).templateId(string) — the template in use at capture, present on originals too.blurData(string) — a tiny base64 PNG data URI you can render as a blurred placeholder while the real image loads.
dataCollectionResponse (raw answers)
This is the survey payload exactly as the iPad stored it. It is preserved unchanged for backwards compatibility, and it has accumulated three generations of shape. If you are writing new code, read survey instead — it flattens all of this for you.
The object has two keys: createdDate (when the guest started answering) and responses (an array).
Each entry in responses may contain:
question— on the oldest entries this is a plain string. On newer entries it is an object withname(the reporting label),text(what the guest was shown),type,optional, andfieldId.answersalso appears onsegmentandpasswordquestions, listing the allowed values.answer— the guest's answer. Absent when they did not answer.checkboxanswers are the string"checked";dateanswers are ISO 8601 strings.answerLabel— present only onmulti_selectandimage_multi_select. This matters: for those two typesansweris a stable choice id likech_01KT95Z76RM2P1HJB0BEDJJYEK, andanswerLabelis the text the guest actually saw. ShowanswerLabel, compare onanswer.fieldId— the stable question id (fld_prefix). It appears both here and insidequestion. It survives renaming the question, so it is the right key to join on.stage—preSession,dynamicElementsorpostCapture. See below.capturedAt— when this specific answer was given.source— an object{ "kind": "event" | "template" | "prompt", "id": "..." }, telling you where the question was defined. Questions with"kind": "template"are the ones printed onto the photo itself.
Entries created before we introduced stable question ids have only question and answer. Handle their absence rather than assuming every key is present.
survey (normalised answers)
The same answers, flattened into one predictable shape. Prefer this. It has two keys:
stages(array of strings) — which stages produced answers, always in guest-flow order:preSession, thendynamicElements, thenpostCapture.answers(array of objects) — one object per answer.
answers is in storage order, which is not chronological order. An answer carried forward from an earlier capture in a take-more session is appended at the end, so its capturedAt can be older than the entries before it. stages is unaffected — it is always emitted in guest-flow order. If you need a timeline, sort on capturedAt yourself.
Each entry in answers always has all eight keys, using null where a value is unknown:
fieldId(string or null) — the stable question id.question(string or null) — the question as shown to the guest.type(string or null) —string,email,phone_number,date,checkbox,full_name,segment,password,multi_select,image_multi_select.stage(string or null).answer— the raw stored value, unchanged. A choice id for the two select types.answerText(string or null) — always human-readable. The resolved choice label for select questions, otherwise the answer as text. This is the field to display and to write into a CRM.capturedAt(string or null).source(object or null) —{ "kind": "...", "id": "..." }.
See the media.uploaded example above for a complete survey object covering all three stages and both select types.
Survey timing — what you can and cannot rely on
The three survey stages are collected at three different moments in the guest's flow, and uploads begin partway through:
Stage | When the guest answers |
| Before the photo is taken. Asked once per session — not repeated on "take more". |
| At template selection, before the photo is taken. These are the answers printed onto the photo. They are collected per capture — a guest who chooses "take more" answers them again. |
| On the preview screen, after the iPad has already started uploading. Asked once per session — not repeated on "take more". |
Consequences:
media.uploadedcarries a snapshot, not a final record. Each delivery contains everything the session knew at the moment that file finished uploading. On a fast connection the first file can be delivered before the guest has finished the post-capture questions, so itssurveyis smaller than a later file's.Answers only ever grow. Later deliveries for a session are supersets of earlier ones. Merge on
fieldId+capturedAtand take the largest set you have seen for thatsessionId.Use the share webhook when you need the complete set. A guest shares after answering everything, so
session.sentcarries the settled record. If the guest never shares, use our API to read the session later.Repeated
fieldIds are normal, not duplicates. A "take more" guest answers the same template question once per capture; the entries are distinguished bycapturedAt.
Take more: which answer is printed on which photo
After a "take more", the payload carries the dynamic-element answers from every capture in the session — but each templated photo was rendered with only one of them. There is no field linking a media file to the answer that produced it.
Match on time instead: for a given photo, the answer that was printed on it is the latest dynamicElements entry whose capturedAt is at or before that photo's shutterDate (or its createdDate in mediaObjects). A two-capture session looks like this:
capture 1 dynamicElements "One" capturedAt 02:24:25
templated photo shutterDate 02:24:39 -> printed "One"capture 2 dynamicElements "Two" capturedAt 02:24:44
templated photo shutterDate 02:24:57 -> printed "Two"If you only need the most recent answer — a live wall showing the newest photo, say — take the dynamicElements entry with the greatest capturedAt and ignore the rest.
Testing
Open webhook.site and copy your unique URL.
Paste it into the upload and/or share webhook field on the Account page and save.
Launch the event on the iPad. Preview mode does not fire webhooks.
Take a photo to trigger
media.uploaded; share it to triggersession.sent.To stop the webhooks, clear the URL fields and save.
