All docs

Connections

Webhooks and the API

Send signed events to other systems with webhooks, check their signatures, and use the API with a personal access token.

Webhooks

A webhook sends a signed POST to an address of yours when something happens in a repository. Add one in the repository's Settings → Webhooks (repository admins):

  1. Add a webhook and give its payload URL.
  2. Give a secret, or leave it empty to have one made (shown once).
  3. Choose Everything, or Let me select individual events.
EventSent when
Pushes (push)A branch or tag moves: a Git push, a commit from the browser, a submit, or an update from upstream
Changes (change)A change is created, gets a new revision, is merged or abandoned
Reviews (review)Someone approves, asks for changes, or withdraws their vote
Comments (comment)Someone comments on a change
Releases (release)A release is published
Checks (status)CI reports a check

A ping is sent when you add a webhook, and whenever you choose Send a ping to test it.

Deliveries

Each webhook keeps a delivery log, with the payload, the response and its state. A delivery that fails (an error, or no reply) is retried, eight times over about 40 minutes, before it shows as Gave up. Choose Send again to retry one by hand.

Webhooks to private network addresses are refused unless your admin sets INTEGRATIONS_ALLOW_PRIVATE=true.

Checking the signature

Every delivery has these headers:

  • X-Fettlecode-Event: the event, such as change.
  • X-Fettlecode-Delivery: an id for the delivery, the same on retries, so you can ignore repeats.
  • X-Fettlecode-Signature-256: sha256= and the HMAC-SHA256 of the body, with the webhook's secret as the key. The same value is in X-Hub-Signature-256, so code written for GitHub webhooks works unchanged.

In Node.js:

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

function signedByFettlecode(rawBody, header, secret) {
  const expected = `sha256=${createHmac('sha256', secret).update(rawBody).digest('hex')}`;
  return header.length === expected.length && timingSafeEqual(Buffer.from(header), Buffer.from(expected));
}

Check the raw body, exactly as it arrived, before parsing it.

The API

Fettlecode's web app uses an HTTP API at /api, which scripts can use too, with a personal access token:

curl https://code.example.com/api/repos/wren/bookings/changes \
  -H "Authorization: Bearer $FETTLECODE_TOKEN"

A token acts as you, with your access. The commit status API, for reporting CI results, is described in CI and checks. The API isn't versioned yet, so it may change between releases; the webhook payloads and the status API are the stable parts.

For AI tools, use the MCP server instead.