| 1 | --- |
| 2 | title: Webhooks & events |
| 3 | description: Provision GitHub repository webhooks as resources, or subscribe a Cloudflare Worker to repository events with typed payloads and verified deliveries. |
| 4 | --- |
| 5 | |
| 6 | GitHub tells the world what happened to a repository through |
| 7 | webhooks — `POST`s to a URL on every push, pull request, release, |
| 8 | and so on. Alchemy gives you two levels of abstraction: the |
| 9 | `Webhook` resource manages the webhook itself and points it at any |
| 10 | URL, and `consumeRepositoryEvents` subscribes a Cloudflare Worker to |
| 11 | a repository with the webhook, routing, and signature verification |
| 12 | handled for you. |
| 13 | |
| 14 | ## The Webhook resource |
| 15 | |
| 16 | `GitHub.Webhook` manages a repository webhook's lifecycle — created |
| 17 | on the first deploy, updated in place on subsequent deploys, deleted |
| 18 | when the stack is destroyed: |
| 19 | |
| 20 | ```typescript |
| 21 | import * as GitHub from "alchemy/GitHub"; |
| 22 | |
| 23 | yield* GitHub.Webhook("ci-webhook", { |
| 24 | owner: "my-org", |
| 25 | repository: "my-repo", |
| 26 | url: "https://example.com/github", |
| 27 | events: ["push", "pull_request"], |
| 28 | secret, |
| 29 | }); |
| 30 | ``` |
| 31 | |
| 32 | `events` defaults to `["push"]`; pass `["*"]` to receive every event |
| 33 | GitHub emits. The `secret` (a `Redacted` string, e.g. from |
| 34 | `Config.Redacted` — see [Secrets & env](/cloudflare/security/secrets-env)) |
| 35 | makes GitHub sign each delivery with `HMAC-SHA256` in the |
| 36 | `X-Hub-Signature-256` header so your receiver can verify the payload |
| 37 | came from GitHub. |
| 38 | |
| 39 | Credentials come from `GitHub.providers()` — env, stored PAT, or the |
| 40 | `gh` CLI (see [setup](/github/setup)). The token needs `repo` scope |
| 41 | (admin access to the repository) to manage webhooks. |
| 42 | |
| 43 | ## Point it at another resource |
| 44 | |
| 45 | `url` accepts an `Output<string>`, so the webhook can target a |
| 46 | resource provisioned in the same stack: |
| 47 | |
| 48 | ```typescript |
| 49 | const worker = yield* Cloudflare.Worker("Api", { ... }); |
| 50 | |
| 51 | yield* GitHub.Webhook("repo-webhook", { |
| 52 | owner: "my-org", |
| 53 | repository: "my-repo", |
| 54 | url: worker.url!, |
| 55 | events: ["*"], |
| 56 | }); |
| 57 | ``` |
| 58 | |
| 59 | At this level the receiving end is yours to build: parsing the |
| 60 | payload, routing the delivery path, and verifying signatures. Most |
| 61 | of the time you don't want that — read on. |
| 62 | |
| 63 | ## Consume events from a Worker |
| 64 | |
| 65 | `GitHub.consumeRepositoryEvents` subscribes a Cloudflare Worker to a |
| 66 | repository and replaces all the plumbing: it provisions the webhook |
| 67 | pointing at the Worker, binds the signing secret onto it, claims the |
| 68 | delivery path in `fetch`, verifies each delivery's signature, and |
| 69 | hands your handler a typed payload: |
| 70 | |
| 71 | ```typescript |
| 72 | // src/worker.ts |
| 73 | import * as Cloudflare from "alchemy/Cloudflare"; |
| 74 | import * as GitHub from "alchemy/GitHub"; |
| 75 | import * as Effect from "effect/Effect"; |
| 76 | |
| 77 | export default Cloudflare.Worker( |
| 78 | "Worker", |
| 79 | { main: import.meta.url }, |
| 80 | Effect.gen(function* () { |
| 81 | yield* GitHub.consumeRepositoryEvents( |
| 82 | { |
| 83 | owner: "my-org", |
| 84 | repository: "my-repo", |
| 85 | events: ["push", "pull_request"], |
| 86 | secret, |
| 87 | }, |
| 88 | (event) => { |
| 89 | switch (event.name) { |
| 90 | case "push": |
| 91 | return Effect.log(`push to ${event.payload.ref}`); |
| 92 | case "pull_request": |
| 93 | return Effect.log(`PR: ${event.payload.pull_request.title}`); |
| 94 | } |
| 95 | }, |
| 96 | ); |
| 97 | |
| 98 | // ...your own fetch handler is untouched |
| 99 | }).pipe(Effect.provide(Cloudflare.Workers.GitHubRepositoryEventSourceLive)), |
| 100 | ); |
| 101 | ``` |
| 102 | |
| 103 | `event` is Octokit's `EmitterWebhookEvent`, narrowed to the events |
| 104 | you selected — a `switch` on `event.name` narrows `event.payload` |
| 105 | exhaustively. The stack provides both clouds' providers: |
| 106 | `Layer.mergeAll(Cloudflare.providers(), GitHub.providers())`. |
| 107 | |
| 108 | For the full walkthrough — runtime layer, secret verification, |
| 109 | deploy, and a release-blogging agent built on this event source — |
| 110 | see [React to GitHub events from a Worker](/cloudflare/messaging/github-events). |
| 111 | |
| 112 | ## Cloudflare-only today |
| 113 | |
| 114 | `Cloudflare.Workers.GitHubRepositoryEventSourceLive` is currently |
| 115 | the only implementation of the `RepositoryEventSource` contract — |
| 116 | there is no AWS Lambda event source yet. On other hosts you can |
| 117 | still provision a `GitHub.Webhook` directly with any delivery URL, |
| 118 | but routing and signature verification are yours to implement. |
| 119 | |
| 120 | ## Where next |
| 121 | |
| 122 | - [React to GitHub events from a Worker](/cloudflare/messaging/github-events) |
| 123 | — the goal-oriented guide. |
| 124 | - [Repositories](/github/repository) — create and converge the repos |
| 125 | these webhooks attach to. |
| 126 | - [GitHub setup](/github/setup) — credentials and token scopes. |
| 127 | |
| 128 | Reference: |
| 129 | |
| 130 | - [Webhook API reference](/providers/github/webhook) |