[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-92909":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":12,"openIssues":13,"contributorsCount":14,"subscribersCount":14,"size":14,"stars1d":14,"stars7d":14,"stars30d":14,"stars90d":14,"forks30d":14,"starsTrendScore":14,"compositeScore":15,"rankGlobal":10,"rankLanguage":10,"license":16,"archived":17,"fork":17,"defaultBranch":18,"hasWiki":19,"hasPages":17,"topics":20,"createdAt":10,"pushedAt":10,"updatedAt":25,"readmeContent":26,"aiSummary":27,"trendingCount":14,"starSnapshotCount":14,"syncStatus":13,"lastSyncTime":28,"discoverSource":29},92909,"whook","edaywalid\u002Fwhook","edaywalid","Self-hostable webhook gateway: durably capture, inspect, retry, and replay inbound webhooks. Single Go binary.","https:\u002F\u002Fwhook-gateway.netlify.app\u002F",null,"Go",51,2,0,41.43,"MIT License",false,"main",true,[21,22,23,24],"golang","self-hosted","webhook-gateway","webhooks","2026-07-22 04:02:07","\u003Cdiv align=\"center\">\n\n# whook\n\n**A self-hostable gateway that captures, retries, and replays inbound webhooks.**\n\n[![CI](https:\u002F\u002Fgithub.com\u002Fedaywalid\u002Fwhook\u002Factions\u002Fworkflows\u002Fci.yml\u002Fbadge.svg)](https:\u002F\u002Fgithub.com\u002Fedaywalid\u002Fwhook\u002Factions\u002Fworkflows\u002Fci.yml)\n&nbsp;[![Docker](https:\u002F\u002Fgithub.com\u002Fedaywalid\u002Fwhook\u002Factions\u002Fworkflows\u002Fdocker.yml\u002Fbadge.svg)](https:\u002F\u002Fgithub.com\u002Fedaywalid\u002Fwhook\u002Factions\u002Fworkflows\u002Fdocker.yml)\n&nbsp;![License: MIT](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Flicense-MIT-0f766e)\n&nbsp;![Go 1.25](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FGo-1.25-00ADD8?logo=go&logoColor=white)\n&nbsp;[![Live demo](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Fdemo-whook--gateway.netlify.app-0f766e)](https:\u002F\u002Fwhook-gateway.netlify.app)\n\n[Getting started](#getting-started) · [Documentation](docs\u002F) · [Live demo](https:\u002F\u002Fwhook-gateway.netlify.app) · [Contributing](CONTRIBUTING.md)\n\n\u003C\u002Fdiv>\n\n\u003Cbr \u002F>\n\nwhook sits in front of your application. Providers point at it instead of at you.\nIt captures every inbound webhook the instant it arrives, returns a fast 2xx so\nthe provider is happy, then forwards the event to one or more destinations with\nautomatic retries. Every request is stored durably, so you can list, inspect, and\nreplay anything that came in.\n\n\u003Cpicture>\n  \u003Csource media=\"(prefers-color-scheme: dark)\" srcset=\"docs\u002Fdiagrams\u002Fassets\u002Fcapture-flow-dark.png\">\n  \u003Cimg alt=\"Stripe sends a webhook to whook, which returns a fast 200, saves the event to a durable store, and delivers it to your app\" src=\"docs\u002Fdiagrams\u002Fassets\u002Fcapture-flow-light.png\">\n\u003C\u002Fpicture>\n\n**What you get**\n\n- Durable capture before the provider is acknowledged, so no event is ever lost.\n- Exponential-backoff retries with a budget, then dead-letter for what never lands.\n- Fan-out to many destinations, each with its own filter, retries, and status.\n- A durable record of every request, queryable and replayable from an API or UI.\n- Pluggable signature verification (Stripe, GitHub, Shopify, Slack, Linear), idempotent capture, metrics.\n- One static Go binary. SQLite built in, Postgres when you outgrow it.\n\n## Table of contents\n\n- [The problem](#the-problem)\n- [How it works](#how-it-works)\n- [Getting started](#getting-started)\n- [Documentation](#documentation)\n- [Tech stack](#tech-stack)\n- [Contributing](#contributing)\n- [License](#license)\n\n## The problem\n\nServices like Stripe, GitHub, and Shopify notify you by sending an HTTP POST to a\nURL you give them. A payment succeeds, a pull request opens, an order is placed,\nand they POST the details to your server. That POST is the webhook, and it arrives\nexactly once, at a moment you do not control.\n\nPoint a provider straight at your application and it is fragile:\n\n- If your app is down, deploying, or briefly crashing when the webhook lands, the\n  event is lost. Many providers retry weakly or not at all.\n- When processing fails, the original request is already gone, so debugging is blind.\n- A provider usually allows one destination URL, but billing, email, and analytics\n  may all need the same event, which forces hand-written fan-out glue.\n- There is no simple way to replay a past webhook to reproduce a bug or recover.\n\n\u003Cpicture>\n  \u003Csource media=\"(prefers-color-scheme: dark)\" srcset=\"docs\u002Fdiagrams\u002Fassets\u002Fwithout-whook-dark.png\">\n  \u003Cimg alt=\"Without a gateway, a webhook sent while your app is deploying breaks, the event falls away, and it is lost with no retry and no record\" src=\"docs\u002Fdiagrams\u002Fassets\u002Fwithout-whook-light.png\">\n\u003C\u002Fpicture>\n\n## How it works\n\nCapture is decoupled from delivery. An inbound request is saved durably **before**\nit is acknowledged. Delivery happens afterward, on whook's own schedule, so a\ndestination being down never costs you an event.\n\n\u003Cpicture>\n  \u003Csource media=\"(prefers-color-scheme: dark)\" srcset=\"docs\u002Fdiagrams\u002Fassets\u002Farchitecture-dark.png\">\n  \u003Cimg alt=\"Provider to ingest with a signature gate and a fast 2xx ack, to a durable store, a router, delivery workers, and your services, with dead-letter and replay branches\" src=\"docs\u002Fdiagrams\u002Fassets\u002Farchitecture-light.png\">\n\u003C\u002Fpicture>\n\n**Reliable delivery.** A failed delivery is retried on a deterministic exponential\nschedule (a 429 honors its `Retry-After`). When the retry budget is exhausted the\ndelivery is dead-lettered, so it stops consuming resources and becomes visible for\ninspection and replay.\n\n\u003Cpicture>\n  \u003Csource media=\"(prefers-color-scheme: dark)\" srcset=\"docs\u002Fdiagrams\u002Fassets\u002Fretry-timeline-dark.png\">\n  \u003Cimg alt=\"A failed delivery is retried with growing backoff gaps until one returns 200, or the budget is exhausted and it is dead-lettered\" src=\"docs\u002Fdiagrams\u002Fassets\u002Fretry-timeline-light.png\">\n\u003C\u002Fpicture>\n\n**Fan-out.** One captured event resolves to every matching destination as an\nindependent delivery track, each with its own filter and status. A failing\ndestination never blocks the others.\n\n\u003Cpicture>\n  \u003Csource media=\"(prefers-color-scheme: dark)\" srcset=\"docs\u002Fdiagrams\u002Fassets\u002Ffan-out-dark.png\">\n  \u003Cimg alt=\"One event delivered to billing, email, and analytics as independent tracks, each with its own filter and status\" src=\"docs\u002Fdiagrams\u002Fassets\u002Ffan-out-light.png\">\n\u003C\u002Fpicture>\n\n## Getting started\n\n### Install\n\n**Docker** (prebuilt multi-arch image, recommended):\n\n```sh\ndocker run -p 8080:8080 -v whook-data:\u002Fdata ghcr.io\u002Fedaywalid\u002Fwhook:latest\n```\n\n**Docker Compose** (builds the full stack from source):\n\n```sh\ncp .env.example .env   # set WHOOK_ADMIN_TOKEN and WHOOK_SECRET_KEY\ndocker compose up --build -d\n```\n\n**Prebuilt binary** (Linux, macOS, Windows on amd64 and arm64):\n\nDownload the binary for your platform from the\n[latest release](https:\u002F\u002Fgithub.com\u002Fedaywalid\u002Fwhook\u002Freleases\u002Flatest), then run it.\n\n**From source** (Go 1.25+):\n\n```sh\ngo install github.com\u002Fedaywalid\u002Fwhook\u002Fcmd\u002Fwhook@latest\nwhook\n```\n\nThe gateway listens on `http:\u002F\u002Flocalhost:8080` and stores events in a local SQLite\nfile by default. See [Deployment](docs\u002Fdeployment.md) for Postgres and scaling.\n\n### Send your first webhook\n\n```sh\n# 1. Register a source (the provider integration)\ncurl -X POST localhost:8080\u002Fsources -d '{\"name\":\"stripe\"}'\n\n# 2. Point it at one or more destinations\ncurl -X POST localhost:8080\u002Fdestinations \\\n  -d '{\"source\":\"stripe\",\"url\":\"https:\u002F\u002Fyour-app.example.com\u002Fwebhooks\"}'\n\n# 3. Send a webhook to the gateway\ncurl -X POST localhost:8080\u002Fingest\u002Fstripe \\\n  -H 'Content-Type: application\u002Fjson' \\\n  -d '{\"type\":\"payment.succeeded\",\"amount\":4900}'\n\n# 4. Inspect captured events\ncurl localhost:8080\u002Fevents\n```\n\nOpen `http:\u002F\u002Flocalhost:8080\u002Fui` for the dashboard: it lists events, links to a\nper-event detail page (payload, delivery state, attempt history, replay button),\nand has a dead-letter view.\n\n## Documentation\n\n- **[Configuration](docs\u002Fconfiguration.md)**: every environment variable, auth, secrets, retention, and rate limiting.\n- **[HTTP API](docs\u002Fhttp-api.md)**: endpoints, sources, destinations, the filter spec, and replay.\n- **[Deployment](docs\u002Fdeployment.md)**: Docker, the GHCR image, Compose, building from source, and Postgres.\n- **[Contributing](CONTRIBUTING.md)**: how to build, test, and submit changes.\n\n## Tech stack\n\n- Go, single static binary\n- SQLite by default (pure-Go driver, no cgo) or Postgres for scale, behind one storage interface\n- Standard library HTTP, server-rendered dashboard\n- dbmate-authored migrations, embedded and applied on startup\n- Landing page in `web\u002F` (TanStack Start, React 19, deployed at [whook-gateway.netlify.app](https:\u002F\u002Fwhook-gateway.netlify.app))\n\n## Contributing\n\nIssues and pull requests are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for\nhow to build, run the tests (including the Postgres integration tests), and the\nmigration and code-style conventions.\n\n## License\n\n[MIT](LICENSE)\n","whook 是一个可自托管的 Webhook 网关，用于可靠地捕获、检查、重试和重放外部服务（如 Stripe、GitHub）发送的入站 Webhook。它采用单个静态 Go 二进制部署，支持 SQLite 内置存储与 PostgreSQL 扩展；具备即时响应（2xx）、持久化存储、指数退避重试、多目标分发（fan-out）、签名验证（原生支持 Stripe\u002FGitHub\u002FShopify 等）、事件查询与 UI 回放等核心能力。适用于需要高可靠性 Webhook 接收、调试与重放能力的后端集成场景，尤其适合中小规模 SaaS、支付对接及 DevOps 自动化系统。","2026-07-11 02:30:19","CREATED_QUERY"]