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.

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.

  • 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. 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 when a template was applied to produce this file.

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

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

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

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 a createdDate inside mediaObjects. For "when was this taken", read 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 output, or null.

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

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?