Skip to main content
Notifications let you know when something happens during a presentation — a viewer opens it, watches a slide, books an appointment, or signs a contract. You subscribe to specific event types and AI Presentations delivers them to your webhook endpoint, email inbox, or console log. AI Presentations can also send the presentation link to homeowners on your behalf and emit events when the email is sent, opened, or fails to reach the inbox. Start with Get a Presentation URL via API to create the presentation you’ll track. The Integration overview connects creation, delivery, and follow-up. Subscription management requires a session cookie or an SSO Bearer session token; a presentation-creation API key does not replace a session here.

Automated email delivery

AI Presentations emails the presentation link directly to homeowners when a presentation is created from a journey ingest, and on demand when you call the send endpoint. Each deck has its own delivery configuration with a custom sender name, reply-to address, subject line, preview text, body template, and CTA label. Every send is recorded as a delivery attempt regardless of outcome.
A homeowner with no email address cannot receive the presentation email. Manual sends with override recipients bypass this check; recipient suppression still applies.
A send is skipped in two cases:
  • The homeowner record has no email address. Manual sends with override recipients bypass this check.
  • A recipient is suppressed. Before every send, AI Presentations checks each recipient against delivery outcomes from the last 90 days, across every organization. Addresses match case-insensitively. An address is suppressed after a hard bounce, a spam report, an unsubscribe, or a SendGrid drop with the reason Bounced Address, Invalid, Spam Reporting Address, or Unsubscribed Address. Blocked bounces, deferrals, and other drop reasons do not suppress an address. If any recipient is suppressed, AI Presentations skips the whole send rather than emailing the remaining recipients.
A resubscribe does not lift suppression. An address stays suppressed until its last suppressing event is more than 90 days old. If AI Presentations cannot complete the suppression check, the send is failed, not skipped, and nothing is sent. A send is also failed when AI Presentations could not load the homeowner’s information — for example, when the underlying Demand IQ lookup is unreachable. The attempt row records the true reason instead of blaming the lead’s data, and manual senders see an error toast rather than a blank-personalization email going out. There is no separate enable toggle — if a deck is connected to a journey or you call the send endpoint, the email goes out. Configure per-deck delivery settings from the Email tab in the deck editor, or via the API:
  • GET /api/decks/{deckId}/delivery — read the current delivery config as delivery, plus the sender the deck’s email goes out from as sender. sender.email is the from address, and sender.defaultName is the sender name used when fromName is unset. AI Presentations resolves both the same way it does at send time. See Sender and reply-to.
  • PUT /api/decks/{deckId}/delivery — set sender name (fromName), reply-to address (replyToEmail), subject (emailSubject), preview text (emailPreheader), reminder subject (reminderEmailSubject), reminder switch (reminderEnabled), body (emailBodyTemplate), and CTA label (ctaLabel). AI Presentations trims replyToEmail and stores a blank value as unset. If replyToEmail is not a valid email address, the request returns 400 with { "error": "Reply-to must be a valid email address" } and nothing is saved. reminderEnabled must be true or false. Any other value returns 400 with { "error": "reminderEnabled must be true or false" }. If you omit reminderEnabled, AI Presentations stores false. emailHomePhotoEnabled turns the home photo on or off and must be true or false. Any other value returns 400 with { "error": "emailHomePhotoEnabled must be true or false" }. If you omit emailHomePhotoEnabled, AI Presentations keeps the stored value. New decks start with it off.
  • GET /api/decks/{deckId}/email-preview — return the presentation picture the deck’s next email would carry, as { "url": "https://..." }. url is null when the email would go out without a picture. The request returns 404 with { "error": "Deck not found" } for a deck outside your company.
If emailBodyTemplate or ctaLabel matches the current default exactly, AI Presentations stores it as unset. The deck then follows future changes to the default copy. Five text fields have a maximum length: If any of these fields is too long, the request returns 400 with { "error": "<message>" } naming the field, and nothing is saved. A value exactly at the limit saves. In the Email tab, these inputs stop accepting characters at the limit. If inserting a token chip would push a field past its limit, the editor refuses the insert and shows a toast. It never truncates the token, because a clipped {{token}} would be sent literally.

Follow-up sends

After ingest sends the first email, call POST /api/presentations/send to send a follow-up, such as a reminder. Authenticate with a Bearer token in the Authorization header. Name the presentation with the presentation_id that ingest returned. A company credential only reaches its own company’s presentations.
  • Instead of presentation_id, you can send the deck_id and source_event_id you passed to ingest. Both fields are then required. A request that includes presentation_id together with deck_id or source_event_id returns 400.
  • idempotency_key is required and must be 1 to 64 characters. Each key gets one delivery attempt and at most one email.
  • skip_if is optional and defaults to []. opened skips the send if the homeowner opened an earlier email. Machine opens don’t count. viewed skips the send if the homeowner viewed the presentation.
  • subject_variant must be reminder. Ingest sends the first email, so initial returns 400.
The suppression, parked, failed, first-send, and reminder switch checks always run, whatever skip_if contains. AI Presentations reads the deck’s reminder switch at send time. If reminders are off for the deck, or the deck has no saved email settings, the reminder is skipped with the reason reminder_disabled. Turning the switch off therefore cancels reminders that are already scheduled. A repeated call with the same key never sends a second email. Once the first call finishes, a repeat returns its original response. If a call stops mid-send, AI Presentations lets a new call with the same key take over after 10 minutes. Every skip except no_presentation is recorded as a delivery attempt and emits one email.sent event with its skipReason.

Sender and reply-to

Every homeowner email comes from no-reply@demand-iq.com. The sender name shown to the homeowner is the first of these that is set:
  1. The deck’s sender name (fromName)
  2. Your organization’s name
  3. Demand IQ
Replies go to the first valid address of these:
  1. The deck’s reply-to address (replyToEmail)
  2. Your company contact email
If neither is set, the email has no reply-to, and replies go to no-reply@demand-iq.com. AI Presentations skips a stored address that is not a valid email instead of sending with it. The company name in the email header and footer is the first of these that is set:
  1. The deck’s Company Name from the Branding tab
  2. Your organization’s name
  3. The deck’s sender name (fromName)
  4. Demand IQ
The Email tab preview uses the same order, so it shows the company name AI Presentations sends. Company Name never sets the sender name or the reply-to address. Every presentation email includes an unsubscribe link, whether it is the initial send, a reminder, or a manual send. The HTML footer reads “Don’t want these emails? Unsubscribe”, and the plain-text version ends with an unsubscribe line. AI Presentations also sends the one-click unsubscribe headers that Gmail and Yahoo require, so inbox clients can show their own Unsubscribe button next to the sender. The unsubscribe covers presentation emails only. Other Demand IQ email to the same address is unaffected. Once a homeowner unsubscribes, AI Presentations suppresses the address and skips later sends with the reason suppressed. See Automated email delivery. Test sends carry the link too, so clicking it in your own inbox suppresses your address. The email preview in the Email tab shows the footer link, but it does not unsubscribe anyone.

Default subject line

When a deck has no subject line of its own, AI Presentations uses a default at send time:
  • Dana, your presentation is ready when the homeowner’s first name is known
  • Your presentation is ready when the first name is empty or missing
A subject that contains only whitespace counts as no subject. The default is never saved to the deck, so it never makes the homeowner’s first name a requirement for sending. When a deck has a saved subject, AI Presentations renders it for each homeowner at send time. If any token in the saved subject renders empty or is unknown, AI Presentations sends the default subject instead. For example, {{homeowner.first_name}}, check this out! goes to a homeowner with no first name as Your presentation is ready, not , check this out!. Because of this fallback, subject tokens never park a presentation at missing_data. AI Presentations checks the deck’s copy once, when it creates the presentation. Every later send, including a manual resend and the release of a parked presentation, renders the deck’s current subject. The fallback covers a subject you save after the presentation was created. In the deck editor, the subject field starts empty and shows the default as its placeholder. If you save the Email tab with an empty subject, AI Presentations stores no subject and sends the default. The preview header fills in subject tokens with sample values and applies the same fallback as the sender, so it shows the subject AI Presentations will send. For a deck with no subject yet, the preview shows the default subject.

Reminder subject line

Reminders are off by default. To turn them on for a deck, switch on Send a reminder if the first email isn’t opened in 2 days in the Reminder email section of the Email tab, or set reminderEnabled to true through the API. The switch applies to every presentation sent from the deck. While the switch is off, the Reminder subject line field is disabled. A reminder is the same email with a different subject line. When a deck has no reminder subject, AI Presentations uses Reminder: followed by the first email’s subject at send time. For example, a deck with the subject Your solar quote, {{homeowner.first_name}} sends the reminder Reminder: Your solar quote, Dana. When the deck has no first-email subject either, AI Presentations uses the default subject:
  • Reminder: Dana, your presentation is ready when the homeowner’s first name is known
  • Reminder: Your presentation is ready when the first name is empty or missing
If a token in the first email’s subject renders empty, the reminder falls back the same way the first email does, so it still reads Reminder: followed by the subject the homeowner received first. To override the default, fill in the optional Reminder subject line field, or set reminderEmailSubject through the API. The field’s placeholder shows the default reminder subject. The field accepts the same tokens as the subject line. A saved reminder subject renders as entered. A blank value falls back to the default. If any token in a saved reminder subject renders empty or is unknown, AI Presentations sends the default reminder subject instead. Reminder subject tokens never park a presentation at missing_data. The first email never uses the reminder subject. A reminder reuses the deck’s message body, CTA label, and presentation picture.

Preview text

Preview text is the short line inbox clients show next to the subject, before the homeowner opens the email. AI Presentations places it in a hidden block at the top of the email, so it appears in the inbox row but not in the email body. It is not included in the plain-text version of the email. When a deck has no preview text of its own, AI Presentations uses a default at send time:
  • Prepared for 123 Main St, with answers to your questions any time when the homeowner’s address is known
  • Your custom results are inside, with answers to your questions any time when the address is empty or missing
Preview text that contains only whitespace counts as no preview text. Like the default subject, the default preview text is never saved to the deck, so it never makes the homeowner’s address a requirement for sending. To override the default, fill in the optional Preview text field in the Email tab, or set emailPreheader through the API. The field accepts the same tokens as the subject line. A blank value saves as unset, and the default applies. If any token in saved preview text renders empty or is unknown, AI Presentations sends the default preview text instead. Preview text tokens never park a presentation at missing_data. The preview header in the deck editor shows the subject and preview text together as an inbox row.

Presentation picture

When the presentation picture is enabled for your organization, presentation and reminder emails show a linked picture of the presentation with a play button. The picture sits between the email header and the greeting, and links to the homeowner’s presentation. Its alt text names your company. The CTA button stays below the message body. AI Presentations builds the picture from the first slide with a usable image. Product slides are skipped. If AI Presentations can’t build the picture, the email sends without it and leaves no empty gap. The email preview in the Email tab shows the same picture the sender would use.

Home photo

The Photo of their home switch, marked Beta, sits under Your presentation preview in the Email tab. It is off by default. The switch appears only when the home photo feature is turned on for your account. Contact Demand IQ to turn it on. When the switch is on, AI Presentations looks up a photo of the homeowner’s home at send time. The lookup needs a street address for the homeowner. If AI Presentations finds a photo, the email leads with it in place of the presentation picture:
  • The photo is 4:3 and carries the same play button as the presentation picture. It links to the homeowner’s presentation.
  • Its alt text names the home, for example Your home at 412 Maple Drive. Watch your presentation.
  • The street address appears as a caption under the photo.
  • The reminder email reuses the same photo.
If AI Presentations can’t find a photo, or the homeowner has no street address, the email falls back to the presentation picture when that switch is on. Otherwise the email sends without a picture. The two switches are independent: A missing home photo never stops the email from sending.

Default message body

When a deck has no message body of its own, AI Presentations uses a default at send time. It uses the most personal version whose tokens all render for the homeowner:
  1. Hi Dana, followed by We put together a short presentation for your home at 123 Main St. when the first name and address are known
  2. Hi Dana, followed by We put together a short presentation just for you. when only the first name is known
  3. Hi, followed by We put together a short presentation for your home at 123 Main St. when only the address is known
  4. Hi, followed by We put together a short presentation just for you. when neither is known
Every version ends with Watch it, then ask anything in the chat. You'll get an answer right away. A body that contains only whitespace counts as no body, and the default is never saved to the deck.

Default CTA label

When a deck has no CTA label of its own, the email button reads Watch Your Presentation →. When a deck has a saved body, AI Presentations renders it for each homeowner at send time. If a contact or journey token in the saved body renders empty, AI Presentations sends the default body instead. For example, a body that opens with Hi {{homeowner.first_name}}, never reaches a homeowner with no first name as Hi ,. Because of this fallback, contact and journey tokens in the body never park a presentation at missing_data. Roof and cost tokens in the body are the exception. They stay blocking: a lead whose roof report has not run parks instead of sending. See Roof and cost tokens are not offered in email. The Email tab trims the body before saving it. The email preview applies the same fallback as the sender.

Subject, preview text, and body tokens

The subject line, preview text, and message body accept {{token}} placeholders that AI Presentations resolves per recipient at send time. Under each field, five inline chips insert the most common tokens at your cursor. View all opens a popover with the full list, and typing {{ still opens the autocomplete dropdown. Datetime tokens resolve from the presentation’s datetimeSnapshot (America/Los_Angeles, en-US), the same basis slide narration uses, so a template that says {{today}} renders as January 21, 2026. The CTA button label does not accept tokens.

Roof and cost tokens are not offered in email

The picker deliberately omits roof_squares, roof_area_sqft, roof_pitch, estimated_cost, min_cost, max_cost, cost_per_square, and ten_year_financed_cost. These fields only exist once the roof report has run. Unlike contact tokens, an unresolved roof or cost token in an email body is blocking, with no fallback to the default body: a lead whose report has not run parks instead of sending. Since lazy generation only starts when the homeowner opens the link (POST /api/presentations/{presentationId}/start), an email that never sends is a presentation that never generates — one roof token in a shared template can stall every unmeasured lead for the full 24-hour escalation window. Use these fields in slide narration instead, where a missing value costs one sentence rather than the entire send. Templates that already include one of these tokens keep working — the sender still resolves them so no homeowner receives raw {{braces}}. New templates should leave them out. Trigger a send (or resend) for a specific presentation:
  • POST /api/presentations/{presentationId}/send — send the homeowner email using the deck’s delivery config. Optionally pass { "emails": ["jordan.lee@example.com"] } to override the default recipients. AI Presentations trims each address and accepts up to 20. If any address is invalid or the list has more than 20 entries, the request returns 400 with { "error": "Enter up to 20 valid email addresses" } and nothing is sent. An empty list or no body sends to the homeowner. Returns a delivery_id and a status of sent, skipped, or failed. When a recipient is suppressed, the response has status skipped and an error naming the address and the reason, for example "dana.ortiz@example.com can't be emailed — the recipient unsubscribed". When the suppression check can’t run, the response has status failed and the error "Could not check this address — try again shortly". If the presentation’s own generation failed, the send is refused with a 409 and code PRESENTATION_FAILED — there’s nothing usable to deliver.
Each send emits an email.sent event. When SendGrid reports the recipient has opened the message, AI Presentations records the open and emits an email.opened event — subscribe to it from your existing notification subscriptions to track opens in real time. email.opened fires once per delivery, on the first open SendGrid reports. Opens triggered by mail-client privacy features (such as Apple Mail Privacy Protection prefetching images) count as opens. When you fetch these entries from GET /api/notifications/events, each email.opened entry carries a top-level machineOpen flag: true when every open SendGrid reported for the delivery was automatic (for example, an MPP prefetch), so you can render them as “Email Opened (automatic)” and separate them from real reads. AI Presentations also records every delivery outcome SendGrid reports, including delivered, deferred, bounced, dropped, spam report, unsubscribe, and click events. Failures that need your attention emit their own events: email.bounced, email.dropped, email.complained, and email.unsubscribed. Subscribe to them to find out when a presentation email never reached the homeowner or when a homeowner opts out. There is no email.delivered event; email.sent already covers the send itself.

Compare email copy performance

Call GET /api/delivery/template-performance to compare versions of a template’s email copy. AI Presentations records a new template version when you save a template with changed subject, preview text, body, CTA label, or sender name. Each template has one active version, which is the copy it sends now. A version stores the copy before tokens are filled in. For each version, the response returns how many first emails were sent, delivered, and opened, and how many homeowners started the presentation.
  • AI Presentations counts each presentation once, under the version used for its first email to a recipient outside Demand IQ and your organization’s own users. Manual sends, including sends to override recipients, count. Opens of a later reminder don’t count toward any version.
  • Compare versions on emailOpened, which counts opens by a person over delivered emails. Automatic opens are excluded.
  • started counts homeowners who started the presentation after the first email, over first emails a person opened. A start is the first time the viewer shows slides to a homeowner. Visits by Demand IQ staff, by members of your organization, in rep mode, and by link scanners such as Microsoft Safe Links don’t count. Opened emails sent before AI Presentations began recording starts count as unknown.
  • A version with fewer than minimumDelivered delivered emails has reportable: false. Treat its rates as directional.
  • First sends whose subject or preview text fell back to the default are reported in fallbackSent and left out of the rates, because the homeowner never saw that version’s copy.
  • Sends where every recipient is a Demand IQ staff (@demand-iq.com) address are excluded, as are sends where every recipient is one of your organization’s own users, and deleted presentations. A test send to a staff address before the homeowner’s email doesn’t count as the first email.
  • Delivered and opened counts come only from events for recipients outside Demand IQ. An open by a staff co-recipient doesn’t count.
  • First sends made before AI Presentations recorded template versions appear in a final row with templateVersionId: null.
  • Each version’s copy includes the subject, preview text, body, CTA label, and sender name. fromName is null when the version used your organization’s name.
  • kind tells you where the copy came from: deck for copy saved on a template, default for the current Demand IQ default, previous_default for an earlier Demand IQ default, and unknown for the row with no recorded copy.
  • When you pass deckId, current: true marks the template’s active version. Without deckId, current is always false.
Pass deckId to list every version one template has had, with all-time counts. The active version comes first and is listed from the moment it’s saved, even before its first send. Each version also carries firstSavedAt and firstSentAt, the dates this template first saved and first sent it. since and until are ignored when you pass deckId. Without deckId, the response covers your whole organization. Pass since and until to bound when the first email was sent, or omit both for the last 30 days. Manage a template’s versions with these endpoints:
  • POST /api/delivery/template-performance/{versionId}/activate?deckId=… makes an earlier version active. AI Presentations writes that version’s copy to the template’s saved email copy, and its counts continue. Returns 204.
  • DELETE /api/delivery/template-performance/{versionId}?deckId=… removes an earlier version from the template’s list for good. Saving identical copy later creates a new version with zero counts. Pass untracked as versionId to remove the row of sends made before versions were recorded. Removing the Demand IQ default copy affects only that template. Removing the active version returns 409.
Both endpoints return 404 when the version isn’t on the template’s list or the template belongs to another organization. If email template performance isn’t enabled for your organization, these endpoints return 404.

Event types

AI Presentations emits these events:
Each event includes the presentationId and relevant context. For example, slide.viewed includes the slide ID and how long the viewer spent on it. AI Presentations records one slide.viewed event per slide visit, including fast flips. There is no separate skip event. A view under 2 seconds carries skipped: true, so you can measure which slides viewers skip and filter skips out of dwell-time metrics. If the viewer hides the tab mid-visit, AI Presentations records the time so far and then updates the same event with the full duration when the viewer leaves the slide. narration.played is a companion to slide.viewed for narrated slides: narrationStarted is true only when play() actually resolved (so autoplay-policy failures do not count as a start), narrationCompleted is true when audio played to the end, and secondsPlayed credits only the remaining clip duration on resumed or retried attempts so the total never exceeds secondsAvailable. secondsAvailable is null when the audio duration was never known (for example, playback failed before metadata loaded). Both email.sent and email.opened include the delivery attempt ID (deliveryId) so you can correlate opens back to a specific send. narration.played is high-volume — one row per narrated slide exit. If your activity feed doesn’t need it, pass excludeEventTypes=narration.played on GET /api/notifications/events to hide it without allowlisting every other type. Event payloads carry identifiers, never contact details. No event payload includes a homeowner’s email address, phone number, or name. The one exception is question.asked / question.escalated, whose payloads include the question and answer text for 90 days (see Event retention). When you query the event log, email.sent entries include top-level recipientEmail, skipReason, and variant fields alongside the payload so you can still see who received each send, why a send was skipped, and whether it was a reminder. The four email failure events (email.bounced, email.dropped, email.complained, email.unsubscribed) carry the presentationId, the delivery attempt ID (deliveryId), and SendGrid’s message and event identifiers (sgMessageId, sgEventId). email.bounced adds a bounceType (bounce or blocked) and SendGrid’s bounce classification; email.dropped adds SendGrid’s drop reason (for example Bounced Address or Unsubscribed Address). These payloads never include the recipient’s email address. Email notifications for these events resolve the affected recipient for you at send time. question.escalated carries the same payload shape as question.asked plus an escalationReason field: no_answer when the AI deflected to a human callback, or low_confidence when the AI answered but flagged the answer as uncertain. Any given question emits exactly one of question.asked or question.escalated, so a single question never triggers two notifications. The question.asked payload can also include an optional degradedReason field: no_client, request_failed, empty_response, or invalid_response. It is set when the viewer received a canned answer because of a temporary issue on AI Presentations’s side, not a gap in your content. These answers never emit question.escalated, so no one on your team is asked to follow up. The field is additive and optional, so existing consumers are unaffected.

Deck and slide identity on question events

question.asked and question.escalated carry four optional fields that identify the template and slide the question was asked on: deckId, deckSlideId, slideTitle, and slideIndex (the slide’s 0-based position, the same base as slide.viewed’s slideIndex). The payload’s slideId identifies a slide within a single presentation, so it can’t be compared across presentations. deckId and deckSlideId can, which lets you answer questions like “which slide draws the most questions across this template”. AI Presentations resolves these fields on the server from the presentation itself, never from the request. Each field is omitted when it can’t be resolved — for example, deckId is absent once the template has been deleted. Questions asked outside a presentation (in the template editor or preview) carry none of these fields. These fields ship as payload version 2. The webhook envelope’s event_version is 2 for events that carry them and 1 for older entries, where the fields were never recorded. The change is additive: version 1 payloads are unchanged, and existing consumers keep working without modification.

Conversation and modality on question events

question.asked and question.escalated also carry two optional fields describing how the question was asked and which browser tab asked it: modality (either voice or text, depending on whether speech recognition or typing produced the question) and conversationId (a UUID that persists for the lifetime of the viewer’s tab). Together they let you group interleaved questions from concurrent viewers on the same link into distinct conversations, and split the voice-versus-text mix. Both fields are populated by the viewer’s browser, so each can legitimately be absent:
  • modality is absent when the client could not determine the input source.
  • conversationId is absent when browser storage is unavailable, when the question was asked from an older client that did not send the field, or when the deck is being demoed by a sales rep rather than viewed by a homeowner. When it is absent, group the question by presentationId and timestamp instead.
These fields ship as payload version 3. The webhook envelope’s event_version is 3 for events that carry them, 2 for entries that carry only the deck and slide identity fields, and 1 for older entries. The change is additive: version 1 and version 2 payloads are unchanged, and existing consumers keep working without modification. Both fields survive the 90-day archive alongside the other classification fields.

Started presentations

presentation.started fires when a viewer presses Start on a presentation’s intro screen. Presentations whose template has no intro, and visits that skip the intro, never emit it. Presentation previews never emit it either. The payload carries presentationId and deckId. deckId is null when the template has been deleted. AI Presentations doesn’t send an email notification for this event, and it has no toggle in the template editor’s Settings tab.

Completed presentations

presentation.completed fires when a viewer reaches the last slide of a ready presentation. Reaching the last slide is what counts, so a viewer who skims ahead still completes the presentation. Narration listen-through doesn’t factor in; use narration.played to measure it. The event fires on every visit that reaches the last slide, the same way presentation.opened fires on every open. Rewatches, runs driven by a sales rep, and reloads on the last slide each emit another completion. To calculate a completion rate, count distinct presentationId values rather than events. Presentation previews never emit it. The payload carries presentationId, deckId, and slideId (the last slide). deckId is null when the template has been deleted. The email subscription for this event is off by default. To receive it, turn on Presentation Completed in the template editor’s Settings tab. When it’s on, you get an email for every completion.

Blocked and failed presentations

presentation.blocked fires 24 hours after a presentation parks at missing_data, once for that presentation. The payload carries presentationId, deckId, a missingFields array of the token field names the journey never supplied (for example, email or roof_squares), and a reason of either missing_lead_data or deck_deleted. The field names identify what the journey failed to collect; no lead values are included. The email subscription for this event is on by default because the cause is a journey configuration your team can fix. presentation.generation_failed fires once per presentation when generation dies against an upstream provider. The payload carries presentationId, deckId, the phase where the work stopped, and an errorMessage. The email subscription for this event is off by default: the cause is on our side rather than yours, and an upstream outage would otherwise email every company about every affected lead. Any organization that wants to see these can turn the subscription on in the template editor’s Settings tab.

Create a subscription

AI Presentations provisions a default set of email subscriptions for your organization the first time a user signs in, so the standard “someone opened your presentation” and “someone asked your AI a question” emails start working without any setup. When new event types ship, they’re added to existing organizations on the next sign-in and inherit the org’s prevailing posture — if you’ve muted every other email, the new type is muted too. To add or change recipients, open the Settings tab in the template editor. You can also create subscriptions via the API. Each subscription needs:
  • Name — a label for your reference, e.g. “Slack — new presentation opened”
  • Event type — which event to listen for (e.g. presentation.opened)
  • Channel — how to deliver the notification
  • Channel config — channel-specific settings (see below)
  • Template scope (optional) — limit to events from a specific template, or leave blank for all templates

Delivery channels

Sends an HTTP POST request to your URL with the event payload as JSON.Channel config:
The webhook payload follows this structure:

Verify the sender

Signing is optional and must be configured for your subscription; creating a subscription through the API does not provision a signing secret. Contact Demand IQ to arrange signing and obtain the shared secret. Until signing is configured, validate a secret Authorization header configured on your subscription over HTTPS. Do not accept an unsigned request as a signed event.Signed requests include X-Webhook-Signature: t=<unix-seconds>,v1=<hex-digest>. Compute HMAC-SHA256 with your shared secret over the timestamp, a period, and the raw request body: timestamp + "." + rawBody. Compare the hexadecimal digest with v1 using a constant-time comparison. Reject missing or malformed signatures and timestamps outside your chosen tolerance (for example, five minutes). Parse the JSON only after verification, and deduplicate by event_id.

Retries and recovery

Return a 2xx promptly after durably accepting the event. Requests time out after 10 seconds. Responses with 429 or 5xx, timeouts, and recognized connection failures are retryable; other HTTP errors are terminal.The current delivery configuration allows five queue processing attempts, normally about two minutes apart (roughly eight minutes from first to fifth attempt). This is not an exact delivery deadline: processing time, backlog, and worker restarts can extend it, and an attempt can fail before reaching your endpoint.After retryable failures exhaust the processing limit, the event is retained for recovery for up to 14 days. Non-retryable failures stop immediately and do not enter that recovery queue. There is no public replay endpoint. Contact Demand IQ with the event_id to investigate recovery; changing a subscription alone does not replay a terminal failure. The event log lets you reconcile activity independently of webhook delivery.

Event versions

event_version identifies the payload schema for that event_type, not the API version or delivery attempt. Version 1 is the initial schema. Later versions are additive unless the change is called out on this page, so accept higher versions you don’t recognize and ignore unknown fields. Review payload changes before relying on new fields. A retry keeps the recorded event version.

Event log

AI Presentations keeps a log of all events for your organization. You can query the log to see what happened during a presentation, filter by event type, or look at events within a time range. For email.sent events, each entry includes top-level recipientEmail, skipReason, and variant fields. AI Presentations resolves all three from the delivery attempt referenced by the payload’s deliveryId; the stored payload itself never contains the address. skipReason is no_email when the homeowner had no email address, suppressed when a recipient previously bounced, reported spam, or unsubscribed, and null for sends that were not skipped. The activity log shows a suppressed skip as “Email Not Sent · previously bounced or unsubscribed”. variant is initial for the first presentation email and reminder for the follow-up sent to a homeowner who never opened it. Entries written before reminders existed return initial, or null when no delivery attempt matches. The presentation activity log labels reminder entries by status and skip reason: A reminder doesn’t change a presentation’s delivery sentAt. sentAt stays the earliest accepted send across all attempts. For email.opened events, each entry includes a top-level machineOpen boolean. It is true when every open SendGrid reported for the delivery attempt was automatic (for example, an Apple Mail Privacy Protection prefetch of the tracking pixel) and false when at least one human open has been recorded. Render machine opens as “Email Opened (automatic)” to keep them separate from real reads. When you scope the query to a single presentation with aggregateId, the response may include a derived presentation.link_opened entry synthesized from the presentation’s first-viewed timestamp. It rides along as an extra row rather than taking a page slot, so it never displaces a stored row and never becomes a nextCursor. Its id is presentation.link_opened:{presentationId} and its payload is { presentationId }. Every presentation that has been opened at least once carries this line, including presentations opened before the derivation shipped — the org-wide feed (/api/activity) still shows stored events only. The event log supports filtering by:
  • Event type — e.g. only contract.signed events
  • Exclude event types — comma-separated list, e.g. narration.played to hide a high-volume type from an activity feed
  • Aggregate type — e.g. only presentation-level events
  • Aggregate ID — events for a specific presentation
  • Time range — since and until parameters
  • Pagination — limit (max 100, default 25) plus a keyset cursor from the previous response’s page.nextCursor. Poll for new activity with recent=1 for the last 90 seconds on the server clock. offset is no longer accepted and requests that send it are rejected with a 400.

Event retention

Event log entries keep their full payloads for 90 days. After that, AI Presentations archives each entry and reduces its payload to identifier and classification fields — for example presentationId, slideIndex, durationMs, the skipped flag, ctaType, and delivery status. The entry itself stays in the log, so activity timelines keep their full history, and skip-rate and dwell-time metrics stay consistent for the life of the presentation. Archived entries carry an archivedAt timestamp. For question.asked and question.escalated, the question and answer text is removed when the entry is archived; the classification fields (such as answerSource and escalationReason), the deck and slide identity fields (deckId, deckSlideId, slideTitle, slideIndex), and the conversation fields (conversationId, modality) remain, so per-slide question metrics and conversation grouping stay consistent past the 90-day window. Archived entries are deleted from the log entirely after two years. AI Presentations keeps an internal archive copy of each entry’s original payload for 9 months after archiving, then deletes it. No raw question or answer text remains in any store AI Presentations controls more than 12 months after the event occurred.

Q&A answer retention

When a homeowner asks a question during a presentation, AI Presentations caches the generated answer, along with its spoken audio, so similar questions on the same deck can be answered instantly. Cached answers follow the same retention limits as the event log. AI Presentations deletes a cached answer after 90 days without use, or 12 months after it was created, whichever comes first. The spoken audio is deleted with it. Expiry has no visible effect during presentations: the next matching question generates a fresh answer.

Managing subscriptions

You can enable, disable, update, or delete subscriptions at any time. Disabling a subscription (isActive: false) stops deliveries without losing the configuration.

API reference

Key notification endpoints:
  • GET/POST /api/notifications/subscriptions — list or create subscriptions
  • GET/PUT/DELETE /api/notifications/subscriptions/{subscriptionId} — manage a specific subscription
  • GET /api/notifications/events — query the event log with filters and keyset pagination. Page forward with cursor from the previous response’s page.nextCursor, or poll with recent=1 for the last 90 seconds on the server clock. The default limit is 25 (cap 100); offset is no longer accepted and requests that send it are rejected with a 400.
  • GET/PUT /api/decks/{deckId}/delivery — manage per-deck email delivery settings
  • POST /api/presentations/{presentationId}/send — send or resend the homeowner email
  • POST /api/presentations/send — send a follow-up email, such as a reminder, for a presentation created by ingest
  • GET /api/delivery/template-performance — compare sent, delivered, and opened counts per email template version
  • POST /api/delivery/template-performance/{versionId}/activate — make an earlier email template version active
  • DELETE /api/delivery/template-performance/{versionId} — remove an earlier email template version from a template’s list
See the API Reference tab for full request and response details.