Skip to main content

Inbound Webhooks

Receive events from GitHub and any system that signs requests with HMAC-SHA256, each request checked against a per-source secret.

Receive events from external systems as they happen. When a commit is pushed to GitHub, or your own service posts a change, the event enters your pipeline with that request.

Push, not poll

SaaS connectors poll on a schedule. A webhook source receives each event when the sender posts it.

Create a webhook source​

In the console, open Pipeline → Sources → Webhooks and create a webhook source in the environment that should receive the events. Bind it to a route so its events reach your sinks. A source with no active route answers 503 and keeps nothing.

Each source gets its own URL and signing secret:

POST https://api.schemabounce.com/api/v1/ingest/webhook/{workspace_id}/{environment_id}/{webhook_id}

Copy the URL from the source in the console and paste it into the sending system.

Signing requests​

Every request must carry an HMAC-SHA256 signature of the raw request body, computed with the source's signing secret and sent as lowercase hex. The header is X-Webhook-Signature unless you set a different header name on the source. A sha256= prefix is accepted.

import hashlib
import hmac

def sign(body: bytes, secret: str) -> str:
return "sha256=" + hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()

# requests.post(url, data=body, headers={"X-Webhook-Signature": sign(body, secret),
# "Content-Type": "application/json"})

Senders that already sign this way work as they are. GitHub does: set the source's signature header to X-Hub-Signature-256 and use the same secret in GitHub. Senders with a different signing scheme are not verified today. That includes Stripe's timestamped Stripe-Signature, Shopify's base64 HMAC and Slack's v0 signature.

When you rotate a source's secret, the previous secret keeps working until the expiry you set, so senders can switch over without dropped events. A source can also restrict which IP addresses may call it.

What becomes an event​

The request body becomes the event's data, unchanged. Send JSON. Up to 10 MB per request.

HeaderEffect
X-Event-TypeSets the event type. Without it the event is an insert.
X-GitHub-EventUsed as the event type when X-Event-Type is absent.
Stripe-Event-TypeUsed as the event type when neither header above is present.
X-Object-TypeOptional object name carried with the event.

Each accepted request is one event. A request the sender retries becomes a second event, so put the sender's own ID in the body if your downstream needs to remove repeats.

Response codes​

CodeMeaning
202Accepted. The body is {"status": "accepted", "event_id": "..."}.
400The URL is malformed or the body could not be read.
401The signature is missing or does not match.
403The source is not active, the IP address is not allowed, or the URL names another workspace or environment.
404No webhook source has this ID.
413The body is over 10 MB.
503The source has no active pipeline route, or ingest is unavailable. Retry with backoff.

Good practice​

  • Retry on 503 with exponential backoff.
  • Rotate the signing secret on a schedule, using the overlap window above.
  • Keep the secret server-side. Anyone holding it can send events as that source.

Continue exploring