Webhooks
GitHub delivers events to one signed endpoint; Gmail is polled, not pushed.
Hark receives events from the outside world in two different ways depending on the integration. GitHub pushes events to a webhook. Gmail does not; it is polled instead. This article covers both, since "why isn't Gmail a webhook" is a fair question once you have read the GitHub side.
GitHub webhook
One endpoint handles every inbound GitHub event for every connected organization:
POST /api/integrations/github/webhookThis single endpoint does two jobs, depending on the event type:
- Support inbound. An
issuesorissue_commentevent on a connected repository becomes a Hark conversation and message, so a GitHub issue can be worked like any other support conversation. - Live development sync.
push,pull_request, andcreate(branch) events that mention a work item key (for exampleENG-12) become development events on that item.installationandinstallation_repositoriesevents keep the App's repository list in sync automatically.
Handled event types
| Event | Action(s) acted on | What happens |
|---|---|---|
issues | opened, reopened, edited, closed | Creates or updates a conversation; closed mirrors the issue's closed state onto the conversation. |
issue_comment | created | Adds a message to the matching conversation. |
push | any | Adds a development event to any work item key mentioned in the push. |
pull_request | any | Adds a development event to any work item key mentioned in the pull request. |
create | branch creation | Adds a development event to any work item key mentioned in the new branch name. |
installation | any | Refreshes which repositories the GitHub App can see for that organization. |
installation_repositories | any | Same, when the set of repositories changes without a full reinstall. |
Any other event type, or a repository the webhook cannot map to a connected organization, is acknowledged with a 200 and otherwise ignored. This is deliberate: GitHub retries deliveries that fail or time out, so events Hark intentionally does not act on are still ACKed rather than left to retry forever.
Verifying the signature
Every delivery must carry a valid X-Hub-Signature-256 header, or the endpoint returns 401 before it parses anything. The header is checked against an HMAC-SHA256 of the raw request body, keyed with your GITHUB_WEBHOOK_SECRET, using a timing-safe comparison:
expected = "sha256=" + hex(HMAC_SHA256(GITHUB_WEBHOOK_SECRET, rawRequestBody))If you are building or debugging anything that calls this endpoint directly (rather than letting GitHub deliver to it), you must compute this signature the same way GitHub does, over the exact raw bytes of the body, before the JSON is parsed. Signing the parsed-and-reserialized object will not match, since re-serialization can change whitespace and key order.
const { createHmac } = require('crypto')
function githubSignature(secret, rawBody) {
return 'sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex')
}Setup
The webhook secret is configured once, in your GitHub App's settings and as the GITHUB_WEBHOOK_SECRET environment variable on your Hark deployment. Both sides must use the same value or every delivery is rejected. Which repositories deliver to this endpoint is controlled by the GitHub App's installation, not by anything in Hark's own UI.
Gmail: polled, not a webhook
Gmail inbound is intentionally different. There is no Gmail webhook endpoint. Instead, a background worker job runs a repeatable poll that syncs new mail for each connected organization, and the same sync logic is available on demand through a manual "Sync now" action. Both paths call the identical underlying sync function, so there is exactly one code path for turning Gmail messages into conversations, whether it runs on its own schedule or because someone asked for a manual refresh.
If you are integrating your own inbound channel and are deciding between a push (webhook) or pull (poll) model, GitHub's webhook is the pattern to follow when the source can reliably deliver signed, retried events to a public endpoint. Polling suits sources (like Gmail) where push delivery would need broader, longer-lived permissions than a periodic pull.