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

# Listeners and Webhooks

> Refresh a collection as soon as the provider notifies you

<Warning>Listeners and webhooks are in beta.</Warning>

A collection's sync runs on its schedule. When the provider can notify you that
something changed, declare a **listener** on the collection so it updates
automatically, soon after the change, instead of at the next scheduled sync.

A notification is a hint. It asks Bijection to sync the collection again; the
records still come from the sync, checked against the collection's evidence
contract like any other. A notification never writes a record by itself, and a
collection with a listener still declares its `sync`.

## Receiving webhooks

A `push` listener receives the provider's webhook deliveries at a URL your
deployment serves. Its `ingress` declares how a delivery is authenticated and
identified:

```ts bijection/billing.ts {6-24} theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
collections: {
  invoices: {
    schema: { … },
    protocol: { … },
    sync: { … },
    listener: {
      kind: "push",
      ingress: {
        delivery: { kind: "body", path: ["id"] },
        authentication: {
          kind: "timestamped_hmac_sha256",
          header: "billing-signature",
          timestamp: { kind: "element", name: "t" },
          signature_element: "v1",
          separator: ".",
          timestamp_unit: "seconds",
          digest: "hex",
          tolerance_seconds: 300,
        },
        body: { kind: "json", validator: v.any() },
        max_body_bytes: 16 * 1024,
        proves: "refresh_hint",
      },
    },
  },
},
```

* `delivery` is where a delivery names itself, in a header or the body.
  Repeated deliveries with the same identity are recognized as duplicates.
* `authentication` is the provider's signature scheme. The signing secret is
  never in the definition: you provide it as a credential when you configure
  the listener.
* `body` and `max_body_bytes` bound what a delivery may contain. Larger
  deliveries are refused before any verification work.
* `proves: "refresh_hint"` makes each accepted delivery request a sync.

The supported signature schemes are:

| `kind` | Provider signs |
| - | - |
| `timestamped_hmac_sha256` | An HMAC-SHA256 over a timestamp and the raw body, checked within `tolerance_seconds` |
| `hmac_sha256` | An HMAC-SHA256 over the raw body alone |
| `es256_jwt` | A JWT signed with a key from the provider's published key set, carrying a digest of the body |
| `shared_token` | Echoes back a token Bijection registered with the provider |

Where the provider opens and closes notification channels through its API, add
a `subscription` naming the HTTP contracts that do it (`watch` and `stop`), and
Bijection manages the channel's lifecycle. Where you register the webhook URL in
the provider's own console, declare no subscription.

### Configuring the listener

Store the provider's signing secret as a credential. Unlike an HTTP
credential, it is the raw secret, not a JSON envelope:

```sh theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
printf '%s' "$SIGNING_SECRET" | bijection integration credential-create BILLING_WEBHOOK_SECRET
```

Then enable the listener on the installed source with your deployment's public
listener URL:

```sh theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
bijection integration listener-configure <source> \
  --callback-url https://<your-deployment-url>/api/integrations/listener \
  --signing-credential BILLING_WEBHOOK_SECRET
```

Bijection appends an endpoint identifier to the URL and reports the full
webhook URL back. Register that URL with the provider. The identifier stays the
same when you disable and re-enable the listener with `--disable`.

`bijection integration listener-status <source>` shows the listener's health
separately from the source's syncs: syncs can keep data current while
notifications are failing, and a healthy listener can coexist with a blocked
sync.

## Polling for changes

When a provider publishes a feed of change events rather than calling you,
declare a `pull` listener instead. It names the HTTP contract to poll, its
polling interval bounds, and how the poll continues from one request to the
next. Each change it observes requests a sync, exactly as a delivery does. A
`pull` listener takes no `--callback-url`.

## Webhook events for operations

Some webhooks announce business events you want to act on one by one, rather
than changes to re-read. Declare `proves: "occurrence"` to keep each verified
event and hand it to one [operation](/operations/overview):

* The delivery identity must be in the body, and the body must be
  `{ kind: "json", validator: v.any() }` with a `max_body_bytes` of at most
  61,440.
* `shared_token` authentication and a `subscription` aren't allowed.

Bind the source to an operation and the event types it accepts:

```sh theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
bijection integration webhook-configure <source> billing:receiveEvent \
  --types invoice.paid,invoice.voided
```

Each event is stored with its identity, deduplicated, and passed to the
operation. The operation reads the verified body with
`ctx.occurrence.payload()`, which returns the exact JSON string the provider
sent. `webhook-status`, `webhook-event` and `webhook-control` inspect and manage
the retained events; see the [CLI reference](/integrations/cli#webhook-events).
