Skip to main content

Webhooks: Technical Documentation

Payload reference for the Booth.Events upload and share webhooks: which events exist, what each is and is not suitable for, and the full JSON of every field.

T
Written by Tim Carr

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 POST over https and 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

event

Fires

Upload webhook URL

media.uploaded

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

session.sent

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.

  • Telling apart files that mediaType puts in the same group — for example the templated AI photo and the templated copy of the original photo in an AI Prompts or AI Portraits session — and putting a session's captures in the order they were taken. See Capture details.

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.uploaded tells you a file exists in Booth.Events, not that it has appeared in your storage folder. Watch the folder itself and join on filename.

  • 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.uploaded fires again for the same media. De-duplicate on deliveryId, and check isNewMediafalse means 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 filename counter 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. To put captures in the order they were taken, sort on takeMoreBatch, then captureIndex (see Capture details).

  • You cannot subscribe per event (gallery), or to a subset of media types. The URLs are account-wide and receive everything. Filter on galleryId / mediaType at your end.

  • media.uploaded is 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 — including dataCollectionResponse and survey when the guest answered nothing. The capture details are an exception: they are sent only when known, and a key that does not apply is left out. 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 url on media.uploaded and the fullUrl on session.sent point at the same file but do not match character for character. Both work. De-duplicate on mediaId, never on the URL.

  • All dates are ISO 8601 strings.

Common envelope

These three keys are on every payload.

Key

Type

Notes

event

string

media.uploaded or session.sent. Branch on this.

deliveryId

string

De-duplication key. For media.uploaded it is the mediaId and is stable across re-deliveries of the same file. For session.sent it is {sessionId}:{epochMillis} and is unique per share, because two shares of the same session are two real events.

apiVersion

string

Payload version, e.g. 2026-08-06. The contract is additive: we add keys, and never remove, rename or re-type an existing one.


Event: media.uploaded

Fields

  • contentType (string) — e.g. image/jpeg, image/gif, video/mp4.

  • isCollage (boolean) — true for a templated photo. A video or animation with the template applied is false; read templateApplied for those.

  • mediaType (string) — original, templated or video. 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 as video even though their contentType is image/gif and their isCollage is false — which is why you should read mediaType rather 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. null for 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 for mediaType or templateApplied.

  • isNewMedia (boolean) — false means 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, otherwise null. 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. sessionIndex increases with upload order, not capture order. For capture order, use takeMoreBatch and captureIndex.

  • dataCollectionResponse (object) — raw survey answers. See dataCollectionResponse. null when the guest answered nothing.

  • survey (object) — the same answers, normalised. See survey. null when 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.

  • Capture details — mediaRole, templateApplied, captureIndex, takeMoreBatch, sessionStartedAt, captureTrigger, filter, sceneApplied, glamIntensity and the AI keys. Sent only when known. See Capture details.

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,
  "mediaRole": "collage",
  "templateApplied": true,
  "takeMoreBatch": 0,
  "sceneApplied": false,
  "glamIntensity": 0.64,
  "captureTrigger": "tap",
  "sessionStartedAt": "2026-07-10T18:11:52.000Z"
}

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 a createdDate inside mediaObjects. For "when was this taken", read the media entries. For when the guest's session started, read sessionStartedAt on the media entries.

  • email (array of strings, or null) — every email address entered on the share screen for this session so far.

  • phoneNumber (array of strings, or null) — 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 survey or dataCollectionResponse.

  • 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 in mediaObjects is the templated photo the guest receives, or null. In AI Prompts and AI Portraits sessions that also keep a templated copy of the original photo, this is the templated AI photo (sessions from older versions of the Booth.Events app may point at either).

  • tenantId, galleryId, galleryName, gallerySlug, galleryDate, galleryPageUrl, sessionPageUrl — as on media.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",
      "mediaRole": "capture",
      "templateApplied": false,
      "captureIndex": 0,
      "takeMoreBatch": 0,
      "sessionStartedAt": "2026-07-10T18:11:52.000Z",
      "sceneApplied": false,
      "glamIntensity": 0.64,
      "captureTrigger": "tap",
      "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",
      "mediaRole": "collage",
      "templateApplied": true,
      "takeMoreBatch": 0,
      "sessionStartedAt": "2026-07-10T18:11:52.000Z",
      "sceneApplied": false,
      "glamIntensity": 0.64,
      "captureTrigger": "tap",
      "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 that media.uploaded sends as mediaId. This is the key to join the two events on.

  • createdDate (string) — capture time.

  • contentType (string).

  • mediaType (string) — original, templated or video, classified exactly as on media.uploaded. Read this rather than deriving it from isCollage, 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. Use fullUrl / thumbUrl.

  • fullUrl, thumbUrl (string) — signed download URLs, valid about 7 days. These will not be character-identical to the url / urlThumb the same file arrived with on media.uploaded, even though they point at the same object.

  • kBytes, kBytesThumb (number).

  • width, height (number) — pixels.

  • isCollage (boolean) — a templated photo. See templateApplied for videos and animations.

  • 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.

  • Capture details (mediaRole, captureIndex, sessionStartedAt and the rest) — present only when known. See Capture details.


Capture details

How each file was captured. media.uploaded sends these keys at the top level of the payload; session.sent sends them on each entry in mediaObjects. The keys and values are identical in both.

These keys are sent only when they are known. Unlike the rest of the payload, a key that does not apply is left out rather than sent as null. Expect them to be missing on files captured by older versions of the Booth.Events app, on files that did not come from the iPad app, and wherever a key does not apply to the file — captureIndex on a templated photo, or the AI keys outside AI sessions, for example.

mediaRole (string) — what the file is within its session. Read this rather than mediaType when you need to tell two templated photos apart.

Value

Meaning

capture

A photo, video, boomerang or slow-mo from the camera. Photos arrive without their template; a video can have its template applied (see templateApplied).

collage

The templated photo the guest receives. In AI Prompts and AI Portraits sessions, this is the templated AI photo.

aiTemplatedOriginal

AI Prompts and AI Portraits only, when the event also keeps the photo from before AI: the templated copy of that original photo. Its mediaType is templated, the same as collage.

montage

An animation built from the session's photos: the one from the GIF capture type, or the one created automatically from a multi-photo session.

  • templateApplied (boolean) — the template's design was applied to this file. Unlike isCollage, it is also true for a video or animation rendered with the template.

  • captureIndex (number) — the capture's position within its round of captures, in the order they were taken, starting at 0. Sent only when mediaRole is capture. A photo the guest retakes keeps its position.

  • takeMoreBatch (number) — which round of captures in the session the file belongs to: 0 for the first, 1 after the guest taps Take more once, and so on. Sort on takeMoreBatch, then captureIndex, to put a session's captures in the order they were taken.

  • sessionStartedAt (string) — when the guest's session started on the iPad. The same on every file in the session, including files from Take more.

  • captureTrigger (string) — what started the capture: tap (started in the iPad app, for example by tapping the screen), tethered (a connected camera fired its own shutter), motion (a 360 spinner booth started recording when the arm began to move) or orcavue (an OrcaVue 360 unit). Handle values you do not recognise.

  • filter (string) — the id of the Color Filter applied, for example blackAndWhiteSepia. Left out when no filter was applied. Templated photos and animations report the filter of their round's first capture.

  • sceneApplied (boolean) — a Scene replaced the background (SceneSelect). Templated photos and animations report true when any capture in their round has a Scene.

  • glamIntensity (number) — the Glam Filter strength set on the template for the round's photos, from 0 to 1 (0.64 is 64%). 0 means the Glam Filter is off; videos, and AI Prompts and AI Portraits captures, always report 0. This is the setting, not a measurement: a photo in which no face is found gets no glam.

  • aiPromptId (string) — AI Prompts only. The AI Prompt used: the same id our API returns for that prompt.

  • aiRerolls (number) — AI Prompts only. How many times the guest re-rolled before this result.

  • aiPortraitFilterId (string) — AI Portraits only. The id of the AI Portrait style used.

  • aiPortraitPersonType (string) — AI Portraits only. man, woman, boy or girl, or auto when none was chosen.


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 with name (the reporting label), text (what the guest was shown), type, optional, and fieldId. answers also appears on segment and password questions, listing the allowed values.

  • answer — the guest's answer. Absent when they did not answer. checkbox answers are the string "checked"; date answers are ISO 8601 strings.

  • answerLabel — present only on multi_select and image_multi_select. This matters: for those two types answer is a stable choice id like ch_01KT95Z76RM2P1HJB0BEDJJYEK, and answerLabel is the text the guest actually saw. Show answerLabel, compare on answer.

  • fieldId — the stable question id (fld_ prefix). It appears both here and inside question. It survives renaming the question, so it is the right key to join on.

  • stagepreSession, dynamicElements or postCapture. 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, then dynamicElements, then postCapture.

  • 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

preSession

Before the photo is taken. Asked once per session — not repeated on "take more".

dynamicElements

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.

postCapture

On the preview screen, after the iPad has already started uploading. Asked once per session — not repeated on "take more".

Consequences:

  • media.uploaded carries 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 its survey is smaller than a later file's.

  • Answers only ever grow. Later deliveries for a session are supersets of earlier ones. Merge on fieldId + capturedAt and take the largest set you have seen for that sessionId.

  • Use the share webhook when you need the complete set. A guest shares after answering everything, so session.sent carries 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 by capturedAt.

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. Files do carry takeMoreBatch, which groups them by round of captures, but survey answers do not, so the link has to be made on time.

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

  1. Open webhook.site and copy your unique URL.

  2. Paste it into the upload and/or share webhook field on the Account page and save.

  3. Launch the event on the iPad. Preview mode does not fire webhooks.

  4. Take a photo to trigger media.uploaded; share it to trigger session.sent.

  5. To stop the webhooks, clear the URL fields and save.

Did this answer your question?