{"id":40717545,"url":"https://github.com/cantabular/hookbot","last_synced_at":"2026-01-21T13:30:59.091Z","repository":{"id":31259339,"uuid":"34821014","full_name":"cantabular/hookbot","owner":"cantabular","description":"Turn webhooks into websockets","archived":false,"fork":false,"pushed_at":"2025-12-12T11:59:31.000Z","size":237,"stargazers_count":44,"open_issues_count":5,"forks_count":8,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-12-14T01:58:34.508Z","etag":null,"topics":["webhooks"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-2-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cantabular.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2015-04-29T22:13:27.000Z","updated_at":"2025-12-12T11:59:33.000Z","dependencies_parsed_at":"2025-01-13T15:21:44.057Z","dependency_job_id":"f021ff7e-b6d3-4f54-a9d1-0509c48a95bc","html_url":"https://github.com/cantabular/hookbot","commit_stats":null,"previous_names":["scraperwiki/hookbot","cantabular/hookbot","sensiblecodeio/hookbot"],"tags_count":19,"template":false,"template_full_name":null,"purl":"pkg:github/cantabular/hookbot","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cantabular%2Fhookbot","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cantabular%2Fhookbot/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cantabular%2Fhookbot/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cantabular%2Fhookbot/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cantabular","download_url":"https://codeload.github.com/cantabular/hookbot/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cantabular%2Fhookbot/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28633760,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-21T04:47:28.174Z","status":"ssl_error","status_checked_at":"2026-01-21T04:47:22.943Z","response_time":86,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["webhooks"],"created_at":"2026-01-21T13:30:59.023Z","updated_at":"2026-01-21T13:30:59.080Z","avatar_url":"https://github.com/cantabular.png","language":"Go","funding_links":[],"categories":["Go"],"sub_categories":[],"readme":"hookbot\n-------\n\nWebhooks. But with outbound connections.\n\nStatus: beta. Things may change. We may need your feedback to make it work for you.\n\nWhat is a web hook?\n-------------------\n\nA web hook is simply a HTTP post request made to your server when something happens.\nFor example, github\n[can generate webhooks in response to various actions](https://developer.github.com/webhooks/),\nsuch as when a branch is pushed to.\n\nThis is useful for creating a continuous deployment infrastructure, which runs tests\nor deploys software when it is updated.\n\n## The problem with web hooks\n\nWebhooks require that the receiver of the hook listens on a port accessible\nto the publisher, and that the publisher must be configured to publish to\nevery listener. In a cloud environment this can be inconvenient, as there may be\nmany receivers interested in an event, and it may not be desirable to poke holes in\nthe firewall for them. In a development environment it may not be easy to listen on\na public TCP interface.\n\nWhy use hookbot?\n----------------\n\nHookbot is a server which makes it so that you can listen on a public port for\nwebhooks in one place.\n\nAny applications wanting to listen to the webhooks can make an outbound websocket\nconnection to a hookbot server, and the hookbot server broadcasts the messages it\nreceives to any interested listeners.\n\nHow do I use hookbot?\n---------------------\n\nHookbot has two types of URL, `https://uri-specific-token@host/pub/\u003cchannel\u003e` and `wss://uri-specific-token@host/sub/\u003cchannel\u003e`.\n\nWebhooks are broadcast from a HTTP POST to `/pub/` and received by making a websocket\nconnection to a `/sub/` URI and reading messages from it.\n\nThe authentication is a hmac of a single secret with the URI, so a token used for\npublishing is different from the one for listening; tokens are also different\nbetween separate channels. The single secret is only known by the server.\n\nHookbot is an easy-to-run portable go server. It just needs the `HOOKBOT_KEY`\nvariable setting, for example like so:\n\n```\n$ HOOKBOT_KEY=foo hookbot serve\n2015/07/22 11:21:58 Listening on :8080\n```\n\nRelease binaries [are hosted on github](https://github.com/sensiblecodeio/hookbot/releases).\n\nIf you're a SensibleCode employee, you can use https://hookbot.scraperwiki.com.\n\n## How do I listen to events\n\nYou can listen to events by making a websocket connection to a `/sub/` URL.\n\nThis can be done with [`wscat`](https://github.com/sensiblecodeio/wscat/releases), for example:\n\n```\n$ wscat wss://token@hookbot.scraperwiki.com/sub/foo/bar\nEVENT!\n```\n\nIf you are writing go code, for convenience a\n[`listen` package is included](https://godoc.org/github.com/sensiblecodeio/hookbot/pkg/listen).\nThis includes a `RetryingWatch` function which returns events.\n\nA brief example is below, or [a more complete example](https://github.com/sensiblecodeio/hanoverd/blob/fcfb97f00a4435eb7d420d75e05ecceb88b27e80/main.go#L322)\ncan be seen in the [hanoverd](https://github.com/sensiblecodeio/hanoverd/) project.\n\n```golang\n\tfinish := make(chan struct{})\n\theader := http.Header{}\n\tevents, errs := listen.RetryingWatch(\"wss://hmac-token@hookbot/sub/github.com/repo/sensiblecodeio/hookbot\", header, finish)\n\n\tgo func() {\n\t\tdefer close(finish)\n\n\t\tfor err := range errs {\n\t\t\tlog.Printf(\"Error in hookbot event stream: %v\", err)\n\t\t}\n\t}()\n\n\tfor payload := range events {\n\t\tlog.Printf(\"Signalled via hookbot, content of payload:\")\n\t\tlog.Printf(\"%s\", payload)\n\t}\n```\n\n\nTLS/SSL support\n---------------\n\nTo allow (and enforce) encrypted connections, provide the paths to your SSL key and\ncertificate:\n\n```\n$ HOOKBOT_KEY=foo hookbot serve --sslkey=/path/to/ssl.key --sslcrt=/path/to/ssl.crt\n```\n\nIf the certificate is signed by a certificate authority, the certFile should be the\nconcatenation of the server's certificate, any intermediates, and the CA's certificate.\n\nGenerating tokens\n-----------------\n\nWith the exception of recursive pub/sub, every endpoint (URI) has its own unique\ntoken to access it. Tokens can be generated by running, for example:\n\n```\n$ HOOKBOT_KEY=foo hookbot make-tokens --url-base https://hookbot.scraperwiki.com /pub/foo/bar\nhttps://2e1150434ba1d8c33bce7c82ee08b5d9850342c7@hookbot.scraperwiki.com/pub/foo/bar\n```\n\nThe `HOOKBOT_KEY` should be kept secret and the authentication token is derived\nfrom it using a [HMAC](https://en.wikipedia.org/wiki/Hash-based_message_authentication_code).\n\nA token which is valid for a URL ending with a `/` is valid for any URL\nbeginning with that prefix up to the `/`. For example a token valid for\n`/pub/foo/` is valid for `/pub/foo/bar`, `/pub/foo/qux`, etc - but not `/pub/notfoo`.\n\nQuery parameters (e.g, ``/foo?query-param`) and hostnames do not contribute to\nthe MAC, only the path part of the URI.\n\nWider scope for publication keys\n--------------------------------\n\nIf you have a valid key for `/pub/foo/`, you can also use that key\nto publish to `/pub/foo/bar/baz` etc. Only `/` terminated substrings (and the\noriginal string itself) are considered when looking for a match; so a valid\nkey for `/pub/foo` is only valid for that particular topic.\n\nListening to multiple endpoints\n-------------------------------\n\nYou can listen to all endpoints below a particular point in the hierarchy.\nFor this, the URI must end with a \"/\".\n\n```\nhookbot make-tokens /sub/foo/\nws://c0..9f@localhost:8080/sub/foo/\n```\n\nYou will receive the path the message was published on\n(i.e. the topic `foo/bar/baz`) followed by a NUL byte, followed by the message.\n(Note the absence of a leading `/pub/` or `/sub/`.)\n\nUnsafe URLs\n-----------\n\nIn addition to `/pub/` and `/sub/`, there is also `/unsafe/pub/` and `/unsafe/sub/`.\nThe unsafe pub URL can be published to without supplying an authentication token,\nbut the `/sub/` URI requires the token. It is so-named because attackers can publish\nmessages to it. This is only reasonable if the authentication is done through\nsome other mechanism, such as certificates or message signing.\n\nA client is prevented from \"accidentally\" listening to `/unsafe/` URLs, which could\nbe dangerous if unintended by requiring that the client supplies an\n[`X-Hookbot-Unsafe-Is-Ok: I understand the security implications`](https://github.com/sensiblecodeio/hookbot/blob/03f7430da914ee6bbebfa264ecddc8b683d52a06/pkg/hookbot/auth.go#L71) header. This prevents clients which have not been designed to connect\nto an unsafe endpoint from doing so.\n\nExtra metadata\n--------------\n\nSometimes, critical webhook data is not passed in-band in the POST body.\nFor example, github passes a signing key as a HTTP header, but applications\nreceiving the webhook might want to know what the key was, as well as the payload.\n\nFor this, `/pub/` URLs can be suffixed with `?extra-metadata=github` which causes\nhookbot to [construct a new payload](https://github.com/sensiblecodeio/hookbot/blob/03f7430da914ee6bbebfa264ecddc8b683d52a06/pkg/hookbot/hookbot.go#L351-L356)\ncarrying the additional information.\n\nUsing routers to rebroadcast organization-wide webhooks to specific repositories\n--------------------------------------------------------------------------------\n\nIf you have many projects and want to have different applications listening,\nit is tedious and problematic to configure github for each project.\nInstead, we can configure\n[GitHub's organization level webhook](https://developer.github.com/v3/orgs/hooks/)\nonce. The problem then is that every project gets details of every other project's\nhooks, which would not be ideal. To avoid this, hookbot can rebroadcast messages\nfrom one channel onto other channels.\n\nFor example, let's say `sensiblecodeio/hookbot`'s `master` branch is updated.\n\nThe `/unsafe/pub/github.com/org/sensiblecodeio?extra-metadata=github` channel receives\nan event. In the payload of the event, the target repository is mentioned, so\na router\n([such as this github router](https://github.com/sensiblecodeio/hookbot/blob/03f7430da914ee6bbebfa264ecddc8b683d52a06/pkg/router/github/github.go#L192))\ncan authenticate and rebroadcast the message to `/sub/github.com/repo/sensiblecodeio/hookbot`.\n\n# License\n\nHookbot is licensed under a BSD-like license.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcantabular%2Fhookbot","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcantabular%2Fhookbot","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcantabular%2Fhookbot/lists"}