Gareth Wilson Gareth Wilson

Guide to Demodesk Webhooks: Features and Best Practices

Published


Demodesk webhooks tell your systems what happens to your sales meetings. When a meeting is scheduled, moved, handed over, started or ended, or when its recording and transcript are ready, Demodesk can send the details to a URL you choose, so your CRM, data warehouse or coaching workflow stays current without polling the API.

This guide covers the events and payload, how to set up an endpoint and authenticate it, the limits you will run into, and how to receive Demodesk webhooks reliably.

What are Demodesk webhooks?

Demodesk webhooks are HTTP POST requests with a JSON body, sent when a meeting (a "demo" in Demodesk's API) or a recording changes state. Company admins manage them in the Demodesk API section of the integration settings. Each endpoint has a URL, one event and optional custom headers.

Demodesk does not sign webhooks. The way to authenticate them is a custom header you add to the endpoint, for example Authorization: Bearer <token>, which Demodesk sends with every request to that endpoint. Your receiver checks that the header is there and that its value matches.

Demodesk webhook features

FeatureDetails
ConfigurationSettings > Integrations > Connections > Demodesk API > Webhooks
Who can manageCompany admins, on eligible paid plans
Events10 meeting and recording events
Events per endpointOne
Method and formatPOST, application/json
AuthenticationOptional custom headers you define
Signature and timestampNone
Event namemeta.event.action
RetriesNon-2xx responses are retried; no schedule published
Failure handlingEndpoints that keep failing are disabled
Endpoint statusHealthy, Failing or Disabled

Demodesk webhook events

EventSent when
demo.scheduledA demo is scheduled
demo.rescheduledA demo is rescheduled
demo.event_response_updatedAttendees' event responses are updated
demo.handoveredA demo is handed over to another host
demo.canceledA demo is cancelled
demo.startedA demo starts
demo.call_note_updatedCall notes are updated for a demo
demo.endedA demo ends
recording.uploadedA recording upload has finished
recording.transcription_postprocessedTranscription and post-processing of a recording are complete

The help article lists all ten events. The older event list in Demodesk's API reference article shows eight, without demo.event_response_updated and demo.call_note_updated, so go by the help article and the V2 OpenAPI reference.

The payload

Every request has the same envelope. meta.event carries the event name in action, an apiVersion (currently v1) and a createdAt timestamp. data follows a JSON:API layout, with an id, a type (demos or recordings) and an attributes object.

A trimmed demo.ended request has this shape (values are illustrative):

{
  "meta": {
    "event": {
      "action": "demo.ended",
      "apiVersion": "v1",
      "createdAt": "2026-10-20T14:32:10Z"
    }
  },
  "data": {
    "id": "48213",
    "type": "demos",
    "attributes": {
      "token": "abc123",
      "name": "Product demo with Acme",
      "hostId": 1021,
      "hostEmail": "host@example.com",
      "startDate": "2026-10-20T14:00:00Z",
      "endDate": "2026-10-20T14:05:00Z",
      "duration": 300,
      "status": "no-show",
      "hubspotDealId": null,
      "salesforceOpportunityId": null
    }
  }
}

Demo attributes include CRM references for Salesforce, HubSpot and Pipedrive, and calendar event IDs for Google and Office 365, many of which can be null. Recording events carry demoId, url and a temporaryVideoUrl, as well as the same host and CRM fields.

Demodesk's FAQ notes one case where you get more than one demo.ended for a meeting: if a meeting is ended manually and then restarted, a second demo.ended arrives with a different status (the FAQ's example of a first status is no-show). Treat the latest one as the truth.

Inspect Demodesk webhooks as they arrive. Add a Hookdeck Inspect URL as a webhook endpoint to see each Demodesk request and its headers, with no account or setup required.

Setting up Demodesk webhooks

You need to be a company admin, and your company needs to be on a plan that includes the Demodesk API. Managers and regular users cannot manage webhook endpoints.

  1. Generate a long random secret, for example with openssl rand -hex 32.
  2. Go to Settings > Integrations > Connections and scroll to the Demodesk API section.
  3. In the Webhooks area, add a webhook.
  4. Enter the Webhook URL.
  5. Select the event for this endpoint.
  6. Add a custom header, for example name Authorization and value Bearer <your secret>.
  7. Click Add endpoint.

Repeat for each event you need. You can use the same URL and header for every endpoint and route on meta.event.action in your receiver.

Custom headers each need a name and a value, and names must be unique. You cannot set Content-Type, Content-Length or Host, because Demodesk manages them. The V2 OpenAPI reference still says to email Demodesk support to enable webhooks, while the current help article describes admins adding endpoints themselves. If you do not see the Webhooks area, check your plan and role, then contact support.

To route through Hookdeck, create a Hookdeck source with the Demodesk type and use its URL as the Webhook URL. The source has two fields:

  • API Key Header: the name of the custom header you added, such as Authorization.
  • API Key: the exact header value, including any Bearer prefix.

The source accepts POST, which is the method Demodesk uses.

Securing Demodesk webhooks

Look up the header you chose and compare its whole value with your secret in constant time.

const crypto = require('crypto');
const express = require('express');

const HEADER = (process.env.DEMODESK_WEBHOOK_HEADER || 'authorization').toLowerCase();
const EXPECTED = process.env.DEMODESK_WEBHOOK_SECRET; // the whole value, e.g. "Bearer 3f9a..."

function safeEqual(received, expected) {
  const a = Buffer.from(received, 'utf8');
  const b = Buffer.from(expected, 'utf8');
  // timingSafeEqual throws on unequal lengths, so check first.
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

const app = express();

app.post('/webhooks/demodesk', express.raw({ type: '*/*' }), (req, res) => {
  const received = req.headers[HEADER];
  if (!EXPECTED || typeof received !== 'string' || !safeEqual(received, EXPECTED)) {
    return res.sendStatus(401);
  }

  let payload;
  try {
    payload = JSON.parse(req.body.toString('utf8'));
  } catch {
    return res.sendStatus(400);
  }

  const action = payload?.meta?.event?.action;
  // Store the payload keyed by action and data.id, then process it asynchronously.
  res.sendStatus(200);
});

The same check in Python:

import hmac
import os

HEADER = os.environ.get("DEMODESK_WEBHOOK_HEADER", "Authorization")
EXPECTED = os.environ.get("DEMODESK_WEBHOOK_SECRET", "")


def verify_demodesk_request(headers) -> bool:
    if not EXPECTED:
        return False  # never accept when unconfigured
    received = headers.get(HEADER)  # use a case-insensitive headers object
    if not received:
        return False
    # Compare bytes: compare_digest raises TypeError on a non-ASCII str.
    return hmac.compare_digest(received.encode("utf-8"), EXPECTED.encode("utf-8"))

A custom header authenticates the sender, not the body. It cannot detect a modified payload, and with no signature or timestamp a captured request can be replayed. Use an HTTPS URL and a long random value, and if the value might have leaked, edit each endpoint in Demodesk with a new one and update your receiver.

Make Demodesk webhooks production-ready. Hookdeck Event Gateway checks your secret header on every request, queues each event, and retries delivery to your service on your schedule, so a slow or failing endpoint does not get disabled in Demodesk.

Demodesk webhook limitations and pain points

Failing endpoints get disabled

The Problem: After a run of failures, Demodesk stops sending to your endpoint, and events during and after the outage never arrive.

Why It Happens: Demodesk retries non-2xx responses, and an endpoint that keeps failing is disabled automatically. The retry schedule and the threshold are not published.

Workarounds:

  • Return 200 as soon as the request is stored, and do the work afterwards.
  • Check endpoint status in the Webhooks list. Failing is your warning before Disabled.
  • After an outage, fetch recent demos and recordings from the V2 API to fill the gap.

How Hookdeck Can Help: Hookdeck accepts each request and responds straight away, so Demodesk sees a healthy endpoint even when your service is down. Events are queued and delivered with retries you control, and Issues notify you when deliveries fail.

One event per endpoint

The Problem: Receiving all ten events means managing ten endpoints, each with its own copy of the header.

Why It Happens: Each webhook endpoint subscribes to a single event.

Workarounds:

  • Use the same URL and header value for every endpoint and route on meta.event.action.
  • Keep a checklist of endpoints so none is missed when you change the secret.

How Hookdeck Can Help: Point every endpoint at one Hookdeck source, then use filters on meta.event.action and separate connections to send each event to the service that needs it.

No delivery ID

The Problem: A retry and a genuine second event look alike.

Why It Happens: Demodesk sends no delivery or event ID, only the resource id and meta.event.createdAt.

Workarounds:

  • Build an idempotency key from meta.event.action, data.id and meta.event.createdAt. Demodesk does not document whether createdAt stays the same on a retry, so also make the processing itself safe to repeat. See our guide to webhook idempotency.

How Hookdeck Can Help: Deduplication can drop requests whose chosen fields match a recent one before they reach your service.

Static header authentication

The Problem: A leaked header value lets anyone send requests your receiver will accept.

Why It Happens: Webhooks are not signed, so the custom header is the only check.

Workarounds:

  • Use HTTPS, a long random value, and change it if it might have leaked.
  • Treat payload contents as a prompt to act, and fetch anything sensitive from the API.

How Hookdeck Can Help: Hookdeck's Demodesk source checks the header on every request. Hookdeck answers Demodesk with a 200 either way, but a request with a missing or wrong value is recorded as failed verification and never reaches your service.

Best practices

Route on meta.event.action

Every request names its event in meta.event.action. Read it first and dispatch from there, rather than relying on which URL received the request.

Expect repeated demo.ended events

A restarted meeting produces a second demo.ended with a different status. Update the record you already have rather than creating a new one.

Use temporary video URLs promptly

Recording events include a temporaryVideoUrl. As the name suggests, do not store it for later; download or process the video soon after the event, or fetch a fresh link from the API.

Acknowledge fast and process asynchronously

Check the header, store the request, return 200, and update your CRM afterwards. Fast responses keep the endpoint Healthy. See why to process webhooks asynchronously.

Conclusion

Demodesk webhooks cover the life of a sales meeting, from scheduling to the finished transcript, in a consistent envelope with the event name in meta.event.action. They are not signed, so authenticate them with a custom header you compare in constant time over HTTPS. Plan for endpoints being disabled after repeated failures, one endpoint per event, and no delivery ID.

Hookdeck Event Gateway checks the header, keeps your endpoint healthy while your service recovers, and routes each event where it needs to go.

Get started with Hookdeck for free and handle Demodesk webhooks reliably in minutes.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.