> ## 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.

# Webhooks Workflows

Webhooks let you react to every new Jam. The workflow recipes below are common patterns, each with steps for one or more no-code platforms, along with a code variant.

Every workflow recipe assumes you already have a `jam.created` webhook endpoint. If you don't, see [Get Started with Webhooks](/webhooks) first.

<CardGroup cols={2}>
  <Card title="Open a PR from a Jam" icon="code-pull-request" href="#open-a-pr-from-a-jam">
    Hand a new Jam to an agent that reads the bug and opens a fix PR.
  </Card>

  <Card title="Notify Slack on incoming Jams" icon="slack" href="#notify-slack-on-incoming-jams">
    Post a Slack message whenever a customer captures a Jam.
  </Card>

  <Card title="Auto-file a Linear or Jira ticket" icon="ticket" href="#auto-file-a-linear-or-jira-ticket">
    Forward every new Jam to your tracker as a structured triage issue.
  </Card>

  <Card title="Prioritize Jams from key accounts" icon="crown" href="#prioritize-jams-from-key-accounts">
    Filter by author domain and route high-value reports straight to the team.
  </Card>
</CardGroup>

## Individual workflows

### Open a PR from a Jam

<Info>
  Hand a new Jam to a coding agent that pulls the full context through [Jam MCP](/jam-mcp), writes a fix, and opens a PR for review. Best for code-addressable bugs from a small set of senders (yourself, your QA team, a beta user).
</Info>

<Warning>
  This workflow runs an LLM agent against your repository. Scope it to a sandbox repo or a narrow set of paths before any merges land near production.
</Warning>

The flow:

1. `jam.created` fires.
2. A GitHub Actions workflow (or any runner you control) runs with the Jam ID.
3. The runner invokes an agent that calls Jam MCP for the recording, logs, network requests, and transcript.
4. The agent writes a patch in a new branch and opens a PR that links back to the Jam.

<Tabs>
  <Tab title="GitHub Actions">
    <Steps>
      <Step title="Add a repository_dispatch workflow">
        Create `.github/workflows/jam-to-pr.yml` and trigger on `repository_dispatch` with event type `jam-created`.
      </Step>

      <Step title="Deploy a small webhook receiver">
        Stand up a function (Netlify, Vercel, Workers) that verifies the Jam signature and POSTs to GitHub's `repository_dispatch` API with the Jam ID in the client payload.
      </Step>

      <Step title="Register the receiver in Jam">
        Add the function URL in [**Settings → Webhooks**](https://jam.dev/s/settings/webhooks) and subscribe to `jam.created`.
      </Step>

      <Step title="Invoke an agent inside the workflow">
        In the workflow, run [Claude Code](https://www.anthropic.com/claude-code) or your agent of choice with the Jam ID. The agent calls Jam MCP for context, edits files, commits, and opens a PR.
      </Step>

      <Step title="Scope the agent and gate the merge">
        Restrict the agent to specific paths, set required reviewers on the PR, and run the test suite before any human approves. 
      </Step>
    </Steps>

    ```yaml theme={"theme":"css-variables"}
    name: Jam to PR
    on:
      repository_dispatch:
        types: [jam-created]

    jobs:
      fix:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - name: Run agent
            env:
              JAM_ID: ${{ github.event.client_payload.jamId }}
              ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
              JAM_API_KEY: ${{ secrets.JAM_API_KEY }}
            run: ./scripts/run-agent.sh
    ```
  </Tab>

  <Tab title="n8n with an agent">
    <Steps>
      <Step title="Trigger on jam.created">
        Add a Webhook trigger and register the URL in Jam.
      </Step>

      <Step title="Call your agent">
        Use the HTTP Request node (or a Claude or OpenAI node) to send the Jam ID and ask the agent to call Jam MCP, write the fix, and push a branch.
      </Step>

      <Step title="Open the PR">
        Add a **GitHub → Create Pull Request** node with the branch name the agent pushed.
      </Step>

      <Step title="Notify the team">
        Add a Slack node so the PR URL lands in your engineering channel for review.
      </Step>
    </Steps>
  </Tab>
</Tabs>

<Tip>
  Pair this with [Auto-file a Linear or Jira ticket](#auto-file-a-linear-or-jira-ticket): the ticket tracks the bug, the PR tracks the fix, and both reference the same Jam URL.
</Tip>

## Team workflows

### Notify Slack on incoming Jams

<Info>
  This workflow posts a Slack message whenever a Jam is captured through a [Recording Link](/recording-links). Pair it with a public-facing Recording Link to collect feedback from your website and ping the team on every new report.
</Info>

<Tabs>
  <Tab title="Zapier">
    Use this [Zapier template](https://zapier.com/templates/details/notify-slack-of-incoming-recording-links-9cb456?secret=MTp0ZW1wbGF0ZTp5T3BoZkNNX2JJTE8wM3VJS2NIak1KZE5LZXdHaWV2V1pydmxLOUZNSVNVOnY3dWppbw) or follow the steps below.

    <Steps>
      <Step title="Create a Zap in Zapier">
        Open [Zapier's create Zap page](https://zapier.com/webintent/create-zap).
      </Step>

      <Step title="Select Webhooks by Zapier as the trigger">
        Choose **Webhooks by Zapier** as the trigger app, then select **Catch Hook**.

        <Frame>
          <img src="https://mintcdn.com/jam-1eb4fd26/RrmeVXBDuZ0Lb3fb/images/zapier-webhooks-catch-hook-selected.png?fit=max&auto=format&n=RrmeVXBDuZ0Lb3fb&q=85&s=81fab35578baa7a822000cbbaad5ca25" alt="Zapier trigger setup with Catch Hook selected for Webhooks by Zapier" width="1564" height="972" data-path="images/zapier-webhooks-catch-hook-selected.png" />
        </Frame>
      </Step>

      <Step title="Copy the webhook URL">
        Open the **Test** tab and copy the webhook URL Zapier generated.

        <Frame>
          <img src="https://mintcdn.com/jam-1eb4fd26/RrmeVXBDuZ0Lb3fb/images/zapier-webhook-url-copy.png?fit=max&auto=format&n=RrmeVXBDuZ0Lb3fb&q=85&s=f6421979e3fe60db0c2f281d812cced3" alt="Zapier Catch Hook test tab with a generated webhook URL and Copy button" width="834" height="972" data-path="images/zapier-webhook-url-copy.png" />
        </Frame>
      </Step>

      <Step title="Create a jam.created webhook in Jam">
        Open [**Settings → Webhooks**](https://jam.dev/s/settings/webhooks), add a new endpoint, paste the Zapier URL, and subscribe to `jam.created`.

        <Frame>
          <img src="https://mintcdn.com/jam-1eb4fd26/RrmeVXBDuZ0Lb3fb/images/webhook-endpoint-jam-created.png?fit=max&auto=format&n=RrmeVXBDuZ0Lb3fb&q=85&s=4143da854f52d6006e97512392f4dc22" alt="Webhook endpoint setup with jam.created selected in Jam" width="2162" height="1884" data-path="images/webhook-endpoint-jam-created.png" />
        </Frame>
      </Step>

      <Step title="Send an example event">
        Open the endpoint's testing view and click **Send Example**.

        <Frame>
          <img src="https://mintcdn.com/jam-1eb4fd26/RrmeVXBDuZ0Lb3fb/images/webhook-endpoint-send-example.png?fit=max&auto=format&n=RrmeVXBDuZ0Lb3fb&q=85&s=c17cd502cd678977f645f855a917ed91" alt="Webhook endpoint testing view with Send Example and the event schema" width="3346" height="2444" data-path="images/webhook-endpoint-send-example.png" />
        </Frame>
      </Step>

      <Step title="Test the Zapier trigger">
        Return to Zapier and test the trigger. Zapier shows a request record from the example event.

        <Frame>
          <img src="https://mintcdn.com/jam-1eb4fd26/RrmeVXBDuZ0Lb3fb/images/zapier-test-record-selected.png?fit=max&auto=format&n=RrmeVXBDuZ0Lb3fb&q=85&s=2209a1b8e9f60f1fbd6a49bb7c659240" alt="Zapier test records view with request A selected from a webhook sample" width="1608" height="2588" data-path="images/zapier-test-record-selected.png" />
        </Frame>
      </Step>

      <Step title="Add a Filter step for Recording Link origin">
        Add **Filter**. Select **Origin**, **Exactly matches**, and `recording_link` to send Slack messages only for Recording Link Jams.
      </Step>

      <Step title="Configure the Slack action">
        Add Slack as the action and map webhook fields into the message text.

        <Frame>
          <img src="https://mintcdn.com/jam-1eb4fd26/RrmeVXBDuZ0Lb3fb/images/zapier-slack-message-fields.png?fit=max&auto=format&n=RrmeVXBDuZ0Lb3fb&q=85&s=d206bfb4bf7db52d3666b2ef0f94b852" alt="Zapier Slack message action with webhook fields mapped into message text" width="4194" height="2528" data-path="images/zapier-slack-message-fields.png" />
        </Frame>
      </Step>

      <Step title="Test and publish the Zap">
        Run a final test, then click **Publish**.

        <Frame>
          <img src="https://mintcdn.com/jam-1eb4fd26/RrmeVXBDuZ0Lb3fb/images/zapier-toolbar-publish.png?fit=max&auto=format&n=RrmeVXBDuZ0Lb3fb&q=85&s=092e9302071ce192da4a62f53a5a8d82" alt="Zapier toolbar with Test run and Publish buttons" width="374" height="94" data-path="images/zapier-toolbar-publish.png" />
        </Frame>
      </Step>
    </Steps>
  </Tab>

  <Tab title="n8n">
    <Steps>
      <Step title="Add a Webhook trigger node">
        Set the HTTP method to **POST** and copy the production URL.
      </Step>

      <Step title="Register the webhook in Jam">
        In [**Settings → Webhooks**](https://jam.dev/s/settings/webhooks), add a new endpoint with the n8n URL and subscribe to `jam.created`.
      </Step>

      <Step title="Add an IF node to filter Recording Link Jams">
        Compare `{{$json["body"]["origin"]}}` to `recording_link`.
      </Step>

      <Step title="Add a Slack node">
        Use the **Post a message** operation and pull the Jam URL from `{{$json["body"]["jamUrl"]}}`.
      </Step>

      <Step title="Activate the workflow">
        Toggle the workflow to **Active** so n8n listens on the production URL.
      </Step>
    </Steps>
  </Tab>

  <Tab title="Make">
    <Steps>
      <Step title="Create a scenario with a Custom webhook trigger">
        Add **Webhooks → Custom webhook** and copy the generated URL.
      </Step>

      <Step title="Register the webhook in Jam">
        In [**Settings → Webhooks**](https://jam.dev/s/settings/webhooks), add the Make URL and subscribe to `jam.created`. Send an example event so Make learns the payload schema.
      </Step>

      <Step title="Add a Filter between trigger and Slack">
        Set the condition `origin` equals `recording_link`.
      </Step>

      <Step title="Add a Slack module">
        Use **Create a message** and reference `jamUrl`, `author.email`, and `title` in the message body.
      </Step>

      <Step title="Turn on the scenario">
        Click **Run once** to verify the flow, then toggle the scheduler on.
      </Step>
    </Steps>
  </Tab>

  <Tab title="Code">
    Deploy this handler to Netlify Functions, Vercel, or Cloudflare Workers, then register the URL in [**Settings → Webhooks**](https://jam.dev/s/settings/webhooks).

    ```ts theme={"theme":"css-variables"}
    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(process.env.WEBHOOK_SECRET);
        event = wh.verify(payload, headers);
      } catch {
        return new Response(null, { status: 400 });
      }

      if (event.origin !== "recording_link") {
        return new Response(null, { status: 200 });
      }

      await fetch(process.env.SLACK_WEBHOOK_URL, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          text: `New Jam from ${event.author.email}: <${event.jamUrl}|${event.title ?? "Untitled Jam"}>`,
        }),
      });

      return new Response(null, { status: 200 });
    };
    ```
  </Tab>
</Tabs>

### Auto-file a Linear or Jira ticket

<Info>
  Every `jam.created` event becomes a triage issue with the title, description, captured URL, and a link back to the Jam.
</Info>

<Tabs>
  <Tab title="Zapier">
    <Steps>
      <Step title="Trigger on jam.created">
        Use **Webhooks by Zapier → Catch Hook**, copy the generated URL, and register it in [**Settings → Webhooks**](https://jam.dev/s/settings/webhooks) with the `jam.created` event.
      </Step>

      <Step title="Send an example event from Jam">
        Open the endpoint in Jam and click **Send Example** so Zapier picks up the payload shape.
      </Step>

      <Step title="Add the Linear or Jira action">
        For Linear, choose **Create Issue**. For Jira, choose **Create Issue**.
      </Step>

      <Step title="Map the fields">
        * **Title**: `title` (fall back to `Bug from <author.email>` if empty)
        * **Description**: combine `description`, `originalUrl`, and `jamUrl` so the Jam is the first link in the ticket
        * **Status:** Triage
        * **Team or Project**: pick the triage destination
        * **Labels**: tag with the Jam `origin`
      </Step>

      <Step title="Publish the Zap">
        Run a test and turn on the Zap.
      </Step>
    </Steps>
  </Tab>

  <Tab title="n8n">
    <Steps>
      <Step title="Add a Webhook trigger node">
        Copy the production URL and register it in Jam with `jam.created`.
      </Step>

      <Step title="Add the tracker node">
        Use **Linear → Create Issue** or **Jira → Create Issue**. Authenticate with an API key.
      </Step>

      <Step title="Map the fields">
        * **Title**: `={{$json.body.title || "Bug from " + $json.body.author.email}}`
        * **Description**: include `{{$json.body.description}}`, the Jam link `{{$json.body.jamUrl}}`, and the captured URL `{{$json.body.originalUrl}}`
        * **Labels**: add the value of `{{$json.body.origin}}`
      </Step>

      <Step title="Activate the workflow">
        Send another example from Jam to confirm the ticket lands.
      </Step>
    </Steps>
  </Tab>

  <Tab title="Make">
    <Steps>
      <Step title="Add a Custom webhook trigger">
        Copy the URL, register it in Jam with `jam.created`, and send an example event so Make learns the schema.
      </Step>

      <Step title="Add the Linear or Jira module">
        Pick **Create an Issue**. Authenticate with your tracker credentials.
      </Step>

      <Step title="Map the fields">
        Use the Jam payload for **Title**, **Description**, and **Labels**. Put the Jam URL on the first line of the description so reviewers click into the recording immediately.
      </Step>

      <Step title="Turn on the scenario">
        Verify with one example event, then enable the scheduler.
      </Step>
    </Steps>
  </Tab>

  <Tab title="Code">
    The example below files a Linear issue. Swap the API call for Jira's REST endpoint if you use Jira.

    ```ts theme={"theme":"css-variables"}
    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(process.env.WEBHOOK_SECRET);
        event = wh.verify(payload, headers);
      } catch {
        return new Response(null, { status: 400 });
      }

      const title = event.title || `Bug from ${event.author.email}`;
      const description = [
        event.jamUrl,
        "",
        event.description ?? "",
        event.originalUrl ? `Captured on: ${event.originalUrl}` : "",
      ].join("\n");

      await fetch("https://api.linear.app/graphql", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          Authorization: process.env.LINEAR_API_KEY,
        },
        body: JSON.stringify({
          query: `
            mutation IssueCreate($input: IssueCreateInput!) {
              issueCreate(input: $input) { success issue { id } }
            }
          `,
          variables: {
            input: {
              teamId: process.env.LINEAR_TEAM_ID,
              title,
              description,
            },
          },
        }),
      });

      return new Response(null, { status: 200 });
    };
    ```
  </Tab>
</Tabs>

## Customer workflows

### Prioritize Jams from key accounts

<Info>
  Route Jams captured on key-accountcaptured on key-account domains straight to the team and tag them with a priority label. Pair it with a Recording Link in your help center or status page so a VIP customer's report never sits in a queue.
</Info>

The flow:

1. `jam.created` fires.
2. Match the `originalUrl` host against a list of key-account domains, or look itMatch the `originalUrl` host against a list of key-account domains, or look it up in your CRM.
3. If matched, post to a high-priority Slack channel and create a `P0` ticket.
4. If not, fall through to your normal triage flow.

<Tabs>
  <Tab title="Zapier">
    <Steps>
      <Step title="Trigger on jam.created">
        Use **Webhooks by Zapier → Catch Hook** and register the URL in Jam.
      </Step>

      <Step title="Add a Paths step">
        Zapier's **Paths** branches on a condition. Create a path called **Key account**.
      </Step>

      <Step title="Filter the Key account path">
        Add a Filter: **Original URL** **Contains** `acme.com` **OR** `bigco.com`. Add one row per domain, or look the host upURL **Contains** `acme.com` **OR** `bigco.com`. Add one row per domain, or look the host up via [Zapier CRM integrations](https://zapier.com/apps/crm-connector/integrations).
      </Step>

      <Step title="Post to a priority Slack channel">
        In the Key account path, add **Slack → Send Channel Message** to `#vip-bugs`. Include the customer email, Jam title, and Jam URL.
      </Step>

      <Step title="Create a P0 ticket">
        Add **Linear → Create Issue** (or Jira). Set **Priority** to `Urgent` and add a `P0` label.
      </Step>

      <Step title="Add the fallback path">
        In the default path, file the Jam in your normal triage queue.
      </Step>

      <Step title="Publish the Zap">
        Test each path with an example event from Jam, then publish.
      </Step>
    </Steps>

    <Tip>
      If you maintain key accounts in HubSpot or Salesforce, swap the static domain list for a CRM lookup step before the Filter.
    </Tip>
  </Tab>

  <Tab title="n8n">
    <Steps>
      <Step title="Trigger on jam.created">
        Add a Webhook trigger and register the URL in Jam.
      </Step>

      <Step title="Branch on the capture URLcapture URL">
        Add an **IF** node. Condition: `{{$json.body.originalUrl}}` contains `originalUrl}}` contains `acme.com`. Use an OR group for additional domains.
      </Step>

      <Step title="Wire the true branch to Slack and Linear">
        Add **Slack → Post a message** to `#vip-bugs` and **Linear → Create Issue** with priority `Urgent`.
      </Step>

      <Step title="Wire the false branch to your default triage">
        Send the payload to your existing flow or leave it to a second webhook listener.
      </Step>

      <Step title="Activate the workflow">
        Send a few example events from Jam with different capture URLcapture URLs to confirm routing.
      </Step>
    </Steps>
  </Tab>

  <Tab title="Code">
    ```ts theme={"theme":"css-variables"}
    import { Webhook } from "svix";

    const KEY_ACCOUNT_DOMAINS = new Set(["acme.com", "bigco.com"]);

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

      let event;
      try {
        const wh = new Webhook(process.env.WEBHOOK_SECRET);
        event = wh.verify(payload, headers);
      } catch {
        return new Response(null, { status: 400 });
      }

      const host = event.originalUrl ? new URL(event.originalUrl).hostname : "";
      const isKeyAccount = [...KEY_ACCOUNT_DOMAINS].some(
        (d) => host === d || host.endsWith(`.${d}`),
      host = event.originalUrl ? new URL(event.originalUrl).hostname : "";
      const isKeyAccount = [...KEY_ACCOUNT_DOMAINS].some(
        (d) => host === d || host.endsWith(`.${d}`),
      );

      if (isKeyAccount) {
        await postToSlack("#vip-bugs", event);
        await createLinearIssue(event, { priority: 1, labels: ["P0"] });
      } else {
        await createLinearIssue(event, { priority: 3, labels: ["triage"] });
      }

      return new Response(null, { status: 200 });
    };
    ```
  </Tab>
</Tabs>

## More patterns

These flows share the same shape: pick up `jam.created`, call [Jam MCP](/jam-mcp) for full context, then do something with it.

<CardGroup cols={2}>
  <Card title="Auto-triage a folder" icon="layers">
    Cluster a folder of Jams (bug bash, beta cycle, customer flow) into grouped tickets instead of one per duplicate.
  </Card>

  <Card title="Design QA into a PR" icon="pen-tool">
    Turn a designer's walk-through into either a fix PR or routed Figma comments.
  </Card>

  <Card title="Recordings into artifacts" icon="file-text">
    Convert walk-throughs into doc drafts, test cases, or release notes with the MCP option to retrieve voice-over transcripts.
  </Card>
</CardGroup>

<Note>
  These patterns rely on Jam MCP for reading Jam content. See [Get Started with MCP](/jam-mcp) for the tool reference.
</Note>
