> ## Documentation Index
> Fetch the complete documentation index at: https://checkfu.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Trigger agents from GitHub and other providers

> Verify a provider webhook and relay its exact bytes into a Checkfu Automation, or bind a deployment-supplied ingress aggregator.

Your users connect GitHub, Linear, or Sentry once; their events fire your agents from then on. There are two honest deployment shapes: use an ingress aggregator your deployment supplies, or host a small verify-and-resign relay in your application. Both end at the same signed Automation ingest route, so dedupe, AdmissionFilter, filtered-delivery, and firing semantics stay identical.

Providers sign webhooks with their own schemes, so they cannot call Checkfu's signed ingest directly. The component that verifies that provider signature is the trust owner. Checkfu never treats an arbitrary POST as a verified GitHub, Linear, or Sentry event.

<Warning>
  Checkfu does not ship a universal provider verifier or perform provider-side webhook registration. D215 removed the former bundled aggregator. `WebhookSource` remains the binding for a deployment that supplies an aggregator; `@checkfu/integration-host` supplies the stock customer-hosted relay kernel when it does not. You still implement or import the selected provider's verifier and register your relay URL with that provider.
</Warning>

Before you start, set the shared variables. `CHECKFU_API_KEY` is bound to the Workspace; no selector header is sent.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
export CHECKFU_API="https://api.checkfu.com"
export KEY="your_api_key"
export WIRE="2026-08-27"
export CHECKFU_WORKSPACE_ID="wrkspc_0123456789abcdef0123456789abcdef"
```

`PRINCIPAL` and `AGENT` are the `prin_…` and `agent_…` the Automation acts as and targets, `WHSEC` is the `whsec_…` ingest secret you mint for it, `CONNECTION` is the `conn_…` from step 1, and `AUTOMATION` is the `auto_…` step 2 returns. The TypeScript tabs below use the [TypeScript SDK](/reference/typescript-sdk) with this client:

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { Checkfu } from "@checkfu/sdk"

const checkfu = new Checkfu({
  apiKey: process.env.CHECKFU_API_KEY,
})
```

<Steps>
  <Step title="Connect the provider" titleSize="h2">
    Authorize a Connection for your end user (see the Connections concept page for the full flow). What matters here: the Connection is owned by a Principal.
  </Step>

  <Step title="Create the webhook Automation" titleSize="h2">
    The Automation is the trigger's brain: its prompt template renders the provider payload as `{{event}}`, its dedupe pointer makes redelivery idempotent, and its admission filter decides which events deserve a Session at all.

    <CodeGroup>
      ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
      curl -s -X POST "$CHECKFU_API/v1/automations" \
        -H "Authorization: Bearer $KEY" -H "Checkfu-Version: $WIRE" -H "$WS" \
        -H "Content-Type: application/json" \
        -d '{
          "name": "triage-issues",
          "created_by": "'$PRINCIPAL'",
          "identity": { "acted_as": "'$PRINCIPAL'" },
          "target": { "kind": "agent_definition", "agent_deployment_id": "'$AGENT_DEPLOYMENT'", "agent_deployment_revision_selection": { "type": "current" } },
          "trigger": {
            "kind": "webhook",
            "dedupe_key": { "kind": "json_pointer", "pointer": "/issue/id" },
            "filter": { "predicates": [ { "kind": "equals", "pointer": "/action", "value": "opened" } ] }
          },
          "input": { "kind": "template", "prompt_template": "Triage this new issue: {{event}}" },
          "ingest_secret": "'$WHSEC'"
        }'
      ```

      ```ts TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
      const automation = await checkfu.automations.create({
        name: "triage-issues",
        created_by: principalId,
        identity: { acted_as: principalId },
        target: {
          kind: "agent_definition",
          agent_deployment_id: agentDeploymentId,
          agent_deployment_revision_selection: { type: "current" },
        },
        trigger: {
          kind: "webhook",
          dedupe_key: { kind: "json_pointer", pointer: "/issue/id" },
          filter: { predicates: [{ kind: "equals", pointer: "/action", value: "opened" }] },
        },
        input: { kind: "template", prompt_template: "Triage this new issue: {{event}}" },
        ingest_secret: ingestSecret,
      })
      ```
    </CodeGroup>
  </Step>

  <Step title="Choose the provider trust owner" titleSize="h2">
    If your deployment has an ingress aggregator, bind the governed Connection as a source. If it does not, skip this binding and use the customer-hosted relay in step 4; the relay calls ordinary signed ingest directly.

    <CodeGroup>
      ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
      curl -s -X POST "$CHECKFU_API/v1/automations/$AUTOMATION/webhook-sources" \
        -H "Authorization: Bearer $KEY" -H "Checkfu-Version: $WIRE" -H "$WS" \
        -H "Content-Type: application/json" \
        -d '{
          "connection_id": "'$CONNECTION'",
          "resource": { "resource_type": "repository", "external_id": "octo/repo" }
        }'
      ```

      ```ts TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
      await checkfu.automations.sources.create(automation.id, {
        connection_id: connectionId,
        resource: { resource_type: "repository", external_id: "octo/repo" },
      })
      ```
    </CodeGroup>

    The binding becomes active when your deployment's aggregator forwards a verified event for that Connection. The `resource` is optional for delivery and useful for provider-side registration bookkeeping.
  </Step>

  <Step title="Verify and relay exact bytes" titleSize="h2">
    Register your aggregator or application receiver URL in the provider's UI/API. For the customer-hosted path, read the raw body before a JSON parser, verify it with the provider's SDK, then relay the exact same bytes:

    ```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
    import { relayProviderWebhook } from "@checkfu/integration-host"

    const result = await relayProviderWebhook({
      delivery: { body: rawBody, headers: providerHeaders },
      automationIngestSecret: process.env.CHECKFU_AUTOMATION_SECRET!,
      verifyProvider: ({ body, headers }) => verifyGitHub(body, headers),
      forwardToAutomation: ({ body, checkfuSignature }) =>
        fetch(`${process.env.CHECKFU_API}/v1/ingest/automations/${automationId}`, {
          method: "POST",
          headers: {
            "content-type": "application/json",
            "checkfu-version": "2026-08-27",
            "checkfu-signature": checkfuSignature,
          },
          body,
        }),
    })
    ```

    A rejected provider signature never reaches the forwarding callback. The helper retains neither credential nor transport; your server owns both.
  </Step>
</Steps>

## What your users see

* **"Why did it fire?"** Every firing links the Automation to the Session and Run it created.
* **"Why didn't it fire?"** `GET /v1/automations/{id}/filtered-deliveries` records every deliberately dropped delivery with a reason. Silence is always explainable.
* Replays are safe: the dedupe pointer settles identical redeliveries against the original firing.

## Cleanup semantics worth knowing

* Deleting a source stops delivery for that binding immediately; deleting the Automation cascades its sources.
* Revoking the Connection ends forwarding without touching the Automation.
* A provider-side webhook you registered out-of-band keeps sending until you remove it at the provider; deliveries for a deleted source are acknowledged and dropped.
