Skip to main content

Documentation Index

Fetch the complete documentation index at: https://jam.dev/docs/llms.txt

Use this file to discover all available pages before exploring further.

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:
  • jam.created: fired when a new Jam is captured from any origin, including the Chrome extension, iOS app, dashboard, Fin (Intercom) integration, or Recording Link. Most integrations should subscribe to this event.
  • intercom.recorder.recorded: fired when a customer submits a screen recording through Jam’s Intercom integration. Use this when tuning Fin AI Agent behavior after a recording arrives mid-conversation.
  • intercom.recorder.opted_out: fired when a customer declines to record through Jam’s Intercom integration. Use this when tuning Fin AI Agent behavior after a customer opts out.
If you are not configuring the Fin AI Agent, you only need jam.created.

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:
  • Delivery logs for each attempt, with success and failure status.
  • Replay for failed deliveries so you can re-fire a payload after fixing your consumer.
  • Signing secret for verifying request authenticity.
  • Success rate and delivery statistics over time.
  • Full payload inspection, including headers and body, for any past delivery.
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 headerDescription
svix-idUnique ID for this delivery. Use it as an idempotency key on your side.
svix-timestampUnix timestamp (seconds) when the delivery was signed.
svix-signatureOne 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

FieldTypeDescription
jamIdstringUnique ID of the Jam.
jamUrlstringShareable URL where the Jam can be viewed.
teamIdstringID of the workspace the Jam belongs to.
typestringOne of video, screenshot, sessionReplay.
createdAtstringISO 8601 timestamp when the Jam was created.
titlestringJam title. May be absent.
descriptionstringJam description. May be absent.
originalUrlstringWebsite URL where the Jam was captured. May be absent.
originstringCapture source, such as recording_link, extension_chrome, mobile_ios, dashboard, or csup_intercom. May be absent.
isIncognitobooleanWhether the Jam was captured in an incognito or private session. May be absent.
authorobjectThe user who captured the Jam. Includes email and may include name.
mediaobjectURLs for the captured media. Includes videoUrl, screenshotUrl, and thumbnailUrl where applicable.
systemInfoobjectCapture environment. Includes browser (name, version), os (name, version), and screen (width, height) where applicable.
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": "[email protected]",
    "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 }
  }
}

FAQ

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:
  • Be available at a publicly accessible HTTPS, non-localhost URL.
  • Respond to the push with HTTP 200 (“OK”).
Each message is attempted based on the following schedule, where each period starts after the preceding attempt fails:
  • Immediately
  • 5 seconds
  • 5 minutes
  • 30 minutes
  • 2 hours
  • 5 hours
  • 10 hours
  • 10 hours, in addition to the previous attempt
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.