Gareth Wilson Gareth Wilson

Guide to Navattic Webhooks: Features and Best Practices

Published


Navattic webhooks send your demo engagement data wherever you need it. When a visitor finishes a session in one of your interactive or AI agent demos, Navattic sends the session, the steps they viewed, who they are and which company they work for, so you can score leads, alert the account owner or load the data into your warehouse.

This guide covers what Navattic's Webhook integration sends and when, the two action types, how to authenticate requests with the Authorization header, and the practices that keep a receiver reliable when much of the delivery behaviour is undocumented.

What are Navattic webhooks?

Navattic sends an event to your Webhook integration whenever a visitor session in your demo is marked as closed. There is one event type, session_closed, and two webhook action types that share the same envelope:

  • A Visitor event carries the full session, the visitor and their company account.
  • An Account event carries only the company account.

Navattic doesn't sign webhook requests. Each request has Content-Type: application/json and User-Agent: navattic-tour, and, only when you configure one on the webhook, an Authorization header with Basic <credentials> or Bearer <token>. That header is the only way to tell a Navattic request from anyone else's.

FeatureDetails
TriggerA visitor session in a demo is marked closed
Event typesession_closed, currently the only value
Action typesVisitor event or Account event
FormatJSON, Content-Type: application/json
User agentnavattic-tour
AuthenticationOptional Authorization header: Basic <credentials> or Bearer <token>
Signature and timestamp headerNone
HTTP methodNot documented
Retries and timeoutNot documented
Source IPsNot published
Change policyNavattic aims to minimise changes and gives notice before breaking changes

Payload

The envelope has three fields: eventType, timestamp (when the session closed, ISO 8601) and data. A Visitor event, trimmed from Navattic's example:

{
  "eventType": "session_closed",
  "timestamp": "2026-07-14T18:32:05.000Z",
  "data": {
    "session": {
      "type": "INTERACTIVE_DEMO",
      "recordedGuideSessionId": "clx8f2k1a0003abcd12345678",
      "createdAt": "2026-07-14T18:20:11.000Z",
      "closedAt": "2026-07-14T18:32:05.000Z",
      "mostRecentDemoName": "Acme Product Tour",
      "recordedEvents": [
        {
          "id": "clx8f3xyz0004abcd12345678",
          "type": "VIEW_STEP",
          "recordedStepName": "Welcome",
          "recordedFlowName": "Getting Started"
        }
      ]
    },
    "endUser": {
      "id": "clx8f1hhh0010abcd12345678",
      "mostRecentEmail": "jane.doe@example.com",
      "mostRecentCompany": "Example Corp",
      "converted": true,
      "engaged": true
    },
    "companyAccount": {
      "id": "clx8f4iii0011abcd12345678",
      "name": "Example Corp",
      "websiteDomain": "example.com",
      "employeeCount": 250,
      "estimatedAnnualRevenue": "50000000"
    }
  }
}

A few details matter for parsing:

  • session.type is INTERACTIVE_DEMO or AGENT_DEMO, and each adds its own fields.
  • companyAccount is null on a Visitor event when no account matched.
  • For an Account event, data is the company account object itself, not wrapped in companyAccount.
  • Large numbers, such as estimatedAnnualRevenue, are strings.
  • When a Launchpad presentation is linked to a Salesforce opportunity, the payload adds opportunityOwnerEmail and opportunityOwnerId.

Nothing in the body names the action type, so check for data.session to tell a Visitor event from an Account event.

Inspect Navattic webhooks as they arrive. Navattic warns that public testing services can expose customer data. A Hookdeck Inspect URL shows the exact method, headers and body Navattic sends, with no account or setup required; send test sessions only.

Setting up Navattic webhooks

Generate a long random token first, for example with openssl rand -hex 32.

  1. In Navattic, add the Webhook integration.
  2. Enter your HTTPS endpoint URL.
  3. Choose the action type: Visitor event or Account event.
  4. Configure an Authorization value on the webhook, either a Bearer token or Basic credentials. Without it, Navattic sends no Authorization header at all.
  5. Complete a session in one of your demos and check that the event arrives.

Navattic's docs describe the payload and headers but not every label in the integration screen, so match these steps to what you see there.

With Hookdeck. Navattic is a supported source in Hookdeck. Its setup note reads: "Paste the full Authorization value Navattic sends, including the "Basic " or "Bearer " prefix." Create a source with the Navattic type and use its URL as the webhook URL. In Authorization Header, paste the whole value, for example Bearer your-long-random-token; for Basic, paste Basic followed by the base64 credentials, not the username and password. The source compares the whole header with that value.

Navattic doesn't document the HTTP method, so the source pre-selects POST without managing it. After your first test session, look at the request in Hookdeck; if its method isn't POST, allow that method on the source too, or requests with it won't be accepted. A request with a missing or wrong Authorization value still gets a 200 from Hookdeck, but Hookdeck marks it as failed verification and doesn't deliver it to your service.

For local development, hookdeck listen 3000 navattic --path /webhooks/navattic gives you a public URL that forwards to your machine.

Securing Navattic webhooks

Compare the whole Authorization header with the value you configured, prefix included, in constant time. There's no need to decode Basic credentials; comparing the full string checks both parts at once.

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

// The full value, e.g. "Bearer your-long-random-token".
const EXPECTED = process.env.NAVATTIC_AUTHORIZATION;

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();

// The method isn't documented, so accept any and check the header first.
app.all('/webhooks/navattic', express.raw({ type: '*/*' }), (req, res) => {
  const received = req.get('authorization');
  if (!EXPECTED || typeof received !== 'string' || !safeEqual(received, EXPECTED)) {
    return res.sendStatus(401);
  }

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

  if (event.eventType === 'session_closed') {
    const isVisitorEvent = Boolean(event.data && event.data.session);
    // Queue the session or account for processing.
  }

  res.sendStatus(200);
});

The same check in Python:

import hmac
import os

EXPECTED = os.environ.get("NAVATTIC_AUTHORIZATION", "")  # e.g. "Bearer your-token"


def verify_navattic_request(headers) -> bool:
    if not EXPECTED:
        return False  # never accept when unconfigured
    received = headers.get("Authorization")
    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 static Authorization value authenticates the sender, not the body, so a modified payload still passes, and nothing stops a captured request from being replayed. Use HTTPS only and a long random value. These payloads carry visitors' names, email addresses and locations, so treat the endpoint and its logs as personal data.

Make Navattic webhooks production-ready. Hookdeck Event Gateway checks the Authorization header, stores every closed session durably, and lets you retry and replay delivery to your CRM or warehouse.

Delivery behaviour is undocumented

The Problem: You don't know the method, how long Navattic waits, or whether a failed request is retried.

Why It Happens: Navattic's Webhook page documents the payload and headers, but not the delivery mechanics.

Workarounds:

  • Capture a real request to learn the method, and accept it explicitly.
  • Respond with a 200 quickly, and alert when a demo with traffic produces no events for longer than usual.

How Hookdeck Can Help: Hookdeck accepts each request as it arrives and stores it, then delivers to your service with retries you configure, so an outage on your side doesn't depend on whether Navattic retries.

One event type, two shapes

The Problem: A handler written for Visitor events breaks on an Account event, or the other way round.

Why It Happens: Both action types send eventType: session_closed. For Account events, data is the company account itself.

Workarounds:

  • Give each action type its own webhook URL or path.
  • If they share a URL, check for data.session before reading anything else.

How Hookdeck Can Help: Hookdeck filters can route on the presence of data.session, so each shape reaches its own handler.

Basic credentials as a pasted value

The Problem: Hookdeck's source or your receiver rejects every request after you switch between Bearer and Basic.

Why It Happens: The comparison is on the full header string. A Basic value has to be compared as Basic <base64>, and the base64 string changes with any change to the username or password.

Workarounds:

  • Prefer a Bearer token: it's one random string, easy to rotate.
  • After any change, capture one request and copy the Authorization value exactly.

How Hookdeck Can Help: Hookdeck keeps failed-verification requests with their headers, so you can compare what Navattic sent with what the source expects.

Best practices

Deduplicate on the session

Use data.session.recordedGuideSessionId for Visitor events, and the account id plus timestamp for Account events, so a repeated delivery doesn't create duplicate CRM activity. See our guide to webhook idempotency.

Treat custom properties as optional

customPropertyValues depends on your workspace's properties and how each was captured (source values such as FORM or ENRICHMENT). Look properties up by name and handle missing ones.

Don't use visitorOpportunityId as an origin signal

Navattic warns that it lives on the visitor record and persists until a newer value replaces it, so every session after a single Launchpad touch carries it forward. Use session.presentation to identify sessions that came through a presentation, and opportunityOwnerEmail to route to the right rep.

Acknowledge fast and process asynchronously

Store the payload, return 200, and push to your CRM in a background job. See why to process webhooks asynchronously.

Conclusion

Navattic's Webhook integration sends a session_closed event when a demo session closes, as a Visitor event with the session, visitor and account or an Account event with the account alone. There's no signature, so configure an Authorization value, compare the full header in constant time, tell the two shapes apart by data.session, and confirm the undocumented method and retry behaviour with a real capture.

Hookdeck Event Gateway checks the header, stores every session, and retries and replays delivery so demo engagement data reaches your systems.

Get started with Hookdeck for free and handle Navattic 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.