Sources
A source is a URL you give a provider — Stripe, GitHub, or anything else that signs its webhooks — so it can send to us. We check each request's signature, turn it into an event, and deliver it to your endpoints exactly like an event you published yourself: same subscriptions, same retries, same dead-letter queue.
Use a source when you want one place for webhook traffic in both directions, or when you want several of your services to receive one provider's events without each one verifying Stripe's signature on its own.
How a request becomes deliveries
- The provider POSTs to the source's ingest URL,
https://webhooks.nimbusnexus.net/v1/ingest/{source_id}. - We verify the signature over the exact bytes received, against the source's signing secret.
- The body must be a JSON object. We read the event type from it (see Event types from a source).
- The event is published into the source's project and matched against your endpoints' subscriptions, which creates one delivery per matching endpoint.
- The provider gets
202. Your endpoints receive the event signed with their secret, not the provider's.
The ingest URL is the only part of Webhooks that takes requests without an API key: the provider's signature is the credential.
Providers and their signatures
| Provider | Header we check | Signed bytes | Replay window |
|---|---|---|---|
stripe | Stripe-Signature: t=…,v1=… | {t}.{body} | 5 minutes |
github | X-Hub-Signature-256: sha256=… | {body} | none (GitHub sends no timestamp) |
generic | X-Webhook-Signature: sha256=… with X-Webhook-Timestamp | {timestamp}.{body} | 5 minutes |
All three are HMAC-SHA256. generic is the same scheme we sign our own deliveries with, so one Webhooks workspace's endpoint can feed another's source, and any sender you control can sign this way in a few lines.
Create a source
In the console: Sources → New source (also on the Overview). Or through the API:
curl -sX POST https://webhooks.nimbusnexus.net/v1/sources \
-H "Authorization: Bearer $ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{
"provider": "stripe",
"name": "Stripe live",
"secret": "whsec_…",
"event_type_prefix": "stripe."
}'- Stripe and GitHub: paste the signing secret the provider shows you. A source created without one starts disabled; add the secret later with
rotate-secretand enable it. - Generic: leave
secretout and we generate one, returned once in this response.
The response carries ingest_url. Paste it into the provider's webhook settings, and add an endpoint that subscribes to the types you expect (prefix stripe. above).
Event types from a source
Each request needs an event type for endpoints to subscribe to. A source finds it, in this order:
- Where you told it to look: a request header, or a dotted JSON path in the body (
data.object.type). - Otherwise the provider's own place: the body's
typefor Stripe and generic, theX-GitHub-Eventheader for GitHub. - If neither gives a value: the source's default type, and failing that,
webhook.received.
The source's prefix goes in front (stripe. + invoice.paid → stripe.invoice.paid), which keeps two providers' types apart in one project. If your event types list shows webhook.received, a source received requests with no type it could read: set a default type or a JSON path on that source.
The provider's own event id (Stripe's evt_…, GitHub's delivery id) is the idempotency key, so when a provider retries an event we already accepted, it gets 202 again and nothing is delivered twice.
What the provider sees
| Status | When |
|---|---|
202 | Accepted and published. |
400 | The body is not JSON, or not a JSON object. |
401 | The signature did not verify — missing, malformed, wrong secret or outside the replay window. One answer for all of them, on purpose. |
402 | The workspace has used its monthly delivery allowance. |
404 | No such source, or the source is disabled. |
413 | The request is over 512 KiB, or the payload over 256 KiB. |
422 | The type is not registered and the event type policy is Strict. |
429 | Too many requests: 600 a minute per source before the signature check, then your plan's publish rate. |
Providers retry on anything but 2xx, so a refusal is not lost on their side — but to see why we refused, turn on recording for the source and read Captured requests.
Rotating a secret
When the provider rolls its secret, send the new one:
curl -sX POST https://webhooks.nimbusnexus.net/v1/sources/src_…/rotate-secret \
-H "Authorization: Bearer $ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{"secret": "whsec_new…"}'The previous secret keeps verifying for 24 hours, so requests signed either way are accepted while the provider switches over. For a generic source, send an empty body and we generate the new secret.
What's next
- Captured requests — see the raw requests a source received, refusals included.
- Sources in the API reference — every source operation, with the SDK and CLI calls.
- Event types — the catalog of types, and what happens to an unknown one.
- Deliveries and retries — what happens after an event is published.
- Webhooks overview — the objects and how they fit together.