docs/webhooks.md
Rama main · réplica de lectura del repositorio original.
# Webhooks
Webhooks let repo owners receive HTTP POST callbacks when events occur in a repository.
## Supported Events
- `push` — fired when commits are pushed (HTTP or SSH receive-pack) or committed in the browser (new file, edit, rename,
delete, profile README, applied suggestion)
- `issues` — fired on issue create, close, reopen, including closes by a closing keyword in a merged PR or a pushed
commit (see [pr-merge](./pr-merge.md#closing-issues)), which fire on the issue's repo
- `pull_request` — fired on PR create, close, merge
## Delivery
Webhooks are dispatched fire-and-forget (`go s.Dispatch(...)`). Each delivery is recorded in `webhook_deliveries` with
the event name, payload, response code, and any error.
**HMAC signing:** if a secret is configured, requests include `X-Hub-Signature-256: sha256=<HMAC-SHA256>`
(GitHub-compatible).
**Retries:** a delivery that gets no response or a non-2xx (including a 3xx) is retried by the worker in
`cmd/server/main.go` after 1 min, 5 min, 30 min and 2 h, five attempts in all. Manual redelivery
(`POST .../hooks/{id}/redeliver?delivery_id=`) goes through the same path.
**Private networks:** every attempt — first delivery, retry and redelivery — dials through `dialPublic`
(`internal/service/netguard.go`, shared with repo import). It resolves the host itself, refuses if any address is
loopback, private, link-local, multicast, unspecified, reserved (`0.0.0.0/8`, `192.0.0.0/24`, `198.18.0.0/15`,
`240.0.0.0/4`), CGNAT (`100.64.0.0/10`), or an IPv6 form that can carry one of those (NAT64, 6to4, site-local), and
connects only to the addresses it vetted, so a DNS answer that changes after the check can't swap in a private one.
Redirects are not followed, and `HTTP_PROXY`/`HTTPS_PROXY` are ignored. A refused delivery is recorded as
`<host> resolves to a private network address`, never the address, and isn't retried; a DNS failure is recorded without
the resolver's address. `Create` runs the same check on the URL's current answer and also requires http or https and a
host; the API answers a refused URL with 400 and the reason. `webhook.allow_local_networks: true` turns both checks off,
for endpoints on your own network; it is separate from `import.allow_local_networks`. Webhooks created before this guard
that point at such an address (a Tailscale `100.x` address, say) now fail every delivery with that error until the
setting is on.
## API Endpoints
All webhook endpoints are under `/api/repos/{owner}/{repo}/hooks`:
| Method | Path | Auth | Description |
| ------ | ------------------------------------------------- | ------------ | ----------------------------------------------------------------- |
| GET | `/api/repos/{owner}/{repo}/hooks/` | — | List webhooks for repo |
| POST | `/api/repos/{owner}/{repo}/hooks/` | Write access | Create webhook (`url`, `secret`, `events`); 400 for a refused URL |
| DELETE | `/api/repos/{owner}/{repo}/hooks/{id}` | Write access | Delete webhook |
| GET | `/api/repos/{owner}/{repo}/hooks/{id}/deliveries` | Write access | List delivery history |
HTMX requests for create/delete swap `fragment-webhooks-list` into `#webhooks-list`.
Default events when `events` is omitted: `push,issues,pull_request`.
## WebhookService (`internal/service/webhook_service.go`)
- `Create(ctx, repoID, url, secret, events)` → `(*Webhook, error)`; a refused URL is a `*WebhookURLError` whose `Reason`
is safe to show
- `ListByRepo(ctx, repoID)` → `([]Webhook, error)`
- `Delete(ctx, id, repoID)` → `error`
- `ListDeliveries(ctx, webhookID)` → `([]WebhookDelivery, error)`
- `Dispatch(repoID, event, payload)` — fire-and-forget; call as `go s.Webhook.Dispatch(...)`
- `PushPayload(repo, pusher, branch, headSHA)` → `map[string]any`
- `IssuePayload(action, repo, issue)` → `map[string]any`
- `PullPayload(action, repo, pr)` → `map[string]any`
## Repo Settings Page
`/{owner}/{repo}/settings` (write access required) — shows a **Collaborators** section, a **Webhooks** section, and (for
repo owners, and org owners on org repos) a **Transfer ownership** danger zone. The collaborators section is only
editable by the repo owner or an org owner (`CanManage`). Collaborators with `admin` role can see the settings page
(write access) but cannot modify collaborators or transfer.