Skip to content

Receive a GitHub webhook

A complete inbound walkthrough: take GitHub repository or organization webhooks, verify the X-Hub-Signature-256 HMAC, and fan them out to your API and a queue — losing nothing if your service is down.

What you'll build

GitHub  ──▶  <ingest-domain>/github-org/events  ──▶  ├─ HTTPS  https://api.acme.in/ci
        (acked + buffered <100ms; signature verified in processing)  └─ SQS    acme-ci-q

1. Create the endpoint

Use the GitHub preset — it configures the X-Hub-Signature-256 HMAC-SHA256 check GitHub signs with and the fast 2xx ack GitHub expects.

bash
curl -X POST https://api.emithook.com/v1/endpoints \
  -H "Authorization: Bearer $EK_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://wh.emhk.in/acme/github-org/events",
    "preset": "github",
    "destinations": ["dst_acme_https", "dst_acme_sqs"]
  }'
json
// → 201 Created
{
  "id": "ep_01JX9...",
  "url": "https://<ingest-domain>/github-org/events",
  "verification": "github",
  "status": "active"
}

TIP

The URL is live immediately on the shared ingest domain (<ingest-domain>, config-driven) — no DNS. Add it under Repository (or Organization) → Settings → Webhooks → Add webhook, content type application/json.

2. Add the webhook secret

Set a Secret on the GitHub webhook form and paste the same value here so Emithook can verify the X-Hub-Signature-256 header:

bash
curl -X PUT https://api.emithook.com/v1/endpoints/ep_01JX9.../secret \
  -H "Authorization: Bearer $EK_KEY" \
  -d '{ "secret": "your-github-webhook-secret" }'

GitHub signs the raw body with HMAC-SHA256 (hex) and sends it as X-Hub-Signature-256: sha256=<hex> — the same scheme Meta uses, so Emithook runs the one shared check. It recomputes the signature over the raw body and compares in constant time. The edge accepts and durably buffers every request in <100 ms (never dropped); verification then runs in the processing plane. A request that fails verification is quarantined — durable and inspectable, but never delivered (not a 401 at the edge, never silently dropped).

3. What an incoming request looks like

http
POST /github-org/events HTTP/1.1
Host: <ingest-domain>
X-GitHub-Event: push
X-Hub-Signature-256: sha256=7d38cd...
Content-Type: application/json

{ "ref": "refs/heads/main", "repository": { "full_name": "acme/api" }, "pusher": { "name": "dev" } }
http
HTTP/1.1 200 OK
{ "received": true }

Emithook acks in under 100 ms, then fans the event out to both destinations independently — each signed, retried, and logged on its own.

4. Confirm delivery

bash
emithook logs tail --endpoint /github-org/events
12:04:31  evt_01JX… push → dst_acme_https   200  142ms  ✓
12:04:31  evt_01JX… push → dst_acme_sqs      enqueued    ✓

If api.acme.in is down, Emithook retries with backoff and parks events behind the circuit breaker — when it recovers, they drain automatically. Nothing is lost.

5. Replay if needed

bash
# replay everything that dead-lettered for this endpoint
emithook replay --dlq --endpoint /github-org/events

See also

Emithook · a Finnoto product