Get Started With Webhooks - Jam Documenation

Jam webhooks

Jam webhooks send HTTP push notifications when a Jam is created. Use them to trigger CI builds, forward bug reports to external systems, post to Slack, file tickets automatically, or start any workflow that should react to a new Jam. Webhooks are scoped to a Jam workspace. Configure them per workspace to match each team’s tooling. Jam supports the following webhook events:

If you are not configuring the Fin AI Agent, you only need jam.created (and recording_link.created, if you track Recording Links).

Getting started

First, build a webhook consumer for Jam’s webhook agent to call. You can deploy your own HTTP server, use a function platform like Netlify Functions, Vercel Functions, or Cloudflare Workers, or test with a service like RequestBin. You can also use no-code and low-code tools such as Zapier and n8n. Once your consumer accepts requests, configure the webhook for your workspace.

A minimal webhook consumer

Webhook URLs are unauthenticated by default. Anyone who learns yours can POST to it. Verifying the svix-signature header confirms the request actually came from Jam. Skip verification only for throwaway test endpoints.

Jam signs webhook deliveries using the Standard Webhooks format, powered by Svix. The easiest way to verify is the official Svix SDK, which handles signature comparison, timestamp tolerance, and key rotation for you. Deployed on Netlify, a basic consumer looks like this:

import { Webhook } from "svix";

export default async (request) => {
  const payload = await request.text();
  const headers = Object.fromEntries(request.headers);

let event;
  try {
    const wh = new Webhook(Netlify.env.get("WEBHOOK_SECRET"));
    event = wh.verify(payload, headers); // throws on invalid signature
  } catch {
    return new Response(null, { status: 400 });
  }

// Do something with the jam, e.g. post to Slack, file a ticket, kick off a CI run.

return new Response(null, { status: 200 });
};

export const config = {
  path: "/my-jam-webhook",
};

If you can’t use the Svix SDK, verify manually. The signature is HMAC-SHA256 over ${svix-id}.${svix-timestamp}.${body}, base64-encoded, and may include multiple space-separated versions during key rotation:

import { createHmac, timingSafeEqual } from "node:crypto";

export default async (request) => {
  const payload = await request.text();
  const svixId = request.headers.get("svix-id");
  const svixTimestamp = request.headers.get("svix-timestamp");
  const svixSignature = request.headers.get("svix-signature");

if (!svixId || !svixTimestamp || !svixSignature) {
    return new Response(null, { status: 400 });
  }

const signed = `${svixId}.${svixTimestamp}.${payload}`;
  const secret = Netlify.env.get("WEBHOOK_SECRET").replace(/^whsec_/, "");
  const expected = createHmac("sha256", Buffer.from(secret, "base64"))
    .update(signed)
    .digest();

const provided = svixSignature
    .split(" ")
    .filter((s) => s.startsWith("v1,"))
    .map((s) => Buffer.from(s.slice(3), "base64"));

const valid = provided.some(
    (p) => p.length === expected.length && timingSafeEqual(p, expected),
  );
  if (!valid) {
    return new Response(null, { status: 400 });
  }

// Handle the jam...

return new Response(null, { status: 200 });
};

export const config = {
  path: "/my-jam-webhook",
};

Creating a webhook

In the Jam dashboard, go to Settings → Webhooks and click Manage to open the Webhook Portal. All endpoint configuration happens in the portal.

Manual endpoint setup

  1. In the portal, click Add Endpoint.
  2. Enter your endpoint URL.
  3. Select the events to subscribe to.
  4. Confirm creation.

Intercom Fin connector
Only relevant if you are tuning Jam’s Intercom Fin AI Agent setup.

  1. Select Intercom Fin from the webhook dropdown.
  2. Paste the URL provided by Intercom.
  3. Subscribe to intercom.recorder.recorded and intercom.recorder.opted_out.
  4. Confirm creation.

After the endpoint is created, copy its Signing secret from the portal (it starts with whsec_). Store it as WEBHOOK_SECRET, or whatever name fits your stack, and use it to verify incoming requests. The webhook is enabled by default. Capture a Jam to test it.

Webhook Portal

The Webhook Portal, also under Settings → Webhooks, gives you operational visibility into every endpoint:

Use the portal to debug consumer issues and to confirm that retries land cleanly once your endpoint is back online.

Webhook payload

The payload arrives as JSON over HTTPS with these headers:

Accept-Charset: utf-8
Content-Type: application/json; charset=utf-8
svix-id: msg_loFOjxBNrRLzqYUf
svix-timestamp: 1715694798
svix-signature: v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=
HTTP header Description
svix-id Unique ID for this delivery. Use it as an idempotency key on your side.
svix-timestamp Unix timestamp (seconds) when the delivery was signed.
svix-signature One or more space-separated v1,<base64-hmac> signatures of ${svix-id}.${svix-timestamp}.${body}. Multiple versions are present during signing-key rotation.

The event name is not sent as a header. Read it from your subscription config or inspect the payload shape directly.

jam.created body

Field Type Description
jamId string Unique ID of the Jam.
jamUrl string Shareable URL where the Jam can be viewed.
teamId string ID of the workspace the Jam belongs to.
type string One of video, screenshot, sessionReplay.
createdAt string ISO 8601 timestamp when the Jam was created.
title string Jam title. May be absent.
description string Jam description. May be absent.
originalUrl string Website URL where the Jam was captured. May be absent.
origin string Capture source, such as recording_link, extension_chrome, mobile_ios, dashboard, or csup_intercom. May be absent.
isIncognito boolean Whether the Jam was captured in an incognito or private session. May be absent.
author object The user who captured the Jam. Includes email and may include name.
media object URLs for the captured media. Includes videoUrl, screenshotUrl, and thumbnailUrl where applicable.
systemInfo object Capture environment. Includes browser (name, version), os (name, version), and screen (width, height) where applicable.
recordingLink object Present only when the Jam was captured via a Recording Link. Includes publicId, type (one_time or reusable), and may include recordingUrl, description, and reference. Use reference to tie the Jam back to your own system.

Required fields: jamId, jamUrl, teamId, type, createdAt, author, media, systemInfo.

Example payload

{
  "jamId": "2174add1-f7c8-44e3-bbf3-2d60b5ea8bc9",
  "jamUrl": "https://jam.dev/c/2174add1-f7c8-44e3-bbf3-2d60b5ea8bc9",
  "teamId": "dc844923-f9a4-40a3-825c-dea7747e57d6",
  "type": "video",
  "createdAt": "2026-05-14T12:53:18.084Z",
  "title": "Checkout button does nothing on Safari",
  "description": "Repro: add item to cart, hit checkout. Console shows a TypeError.",
  "originalUrl": "https://example.com/cart",
  "origin": "recording_link",
  "author": {
    "email": "alex@example.com",
    "name": "Alex Smith"
  },
  "media": {
    "videoUrl": "https://media.jam.dev/.../video.mp4",
    "thumbnailUrl": "https://media.jam.dev/.../thumb.jpg"
  },
  "systemInfo": {
    "browser": { "name": "Safari", "version": "17.4" },
    "os": { "name": "macOS", "version": "14.4.1" },
    "screen": { "width": 1512, "height": 982 }
  },
  "recordingLink": {
    "publicId": "aB3kZ4p",
    "type": "reusable",
    "recordingUrl": "https://example.com/cart",
    "description": "Checkout repro request",
    "reference": "ZD-48217"
  }
}

FAQ

How does a Webhook work?
A webhook push is an HTTP POST request sent to a URL you choose. Jam triggers it automatically when a Jam is created. Your webhook consumer is an HTTP endpoint. It must:

Each message is attempted based on the following schedule, where each period starts after the preceding attempt fails:

If an endpoint is removed or disabled, delivery attempts to the endpoint are disabled as well. For example, an attempt that fails three times before succeeding is delivered roughly 35 minutes and 5 seconds after the first attempt.