Skip to content
NimbusNexus

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

  1. The provider POSTs to the source's ingest URL, https://webhooks.nimbusnexus.net/v1/ingest/{source_id}.
  2. We verify the signature over the exact bytes received, against the source's signing secret.
  3. The body must be a JSON object. We read the event type from it (see Event types from a source).
  4. The event is published into the source's project and matched against your endpoints' subscriptions, which creates one delivery per matching endpoint.
  5. 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

ProviderHeader we checkSigned bytesReplay window
stripeStripe-Signature: t=…,v1=…{t}.{body}5 minutes
githubX-Hub-Signature-256: sha256=…{body}none (GitHub sends no timestamp)
genericX-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:

Shell
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-secret and enable it.
  • Generic: leave secret out 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:

  1. Where you told it to look: a request header, or a dotted JSON path in the body (data.object.type).
  2. Otherwise the provider's own place: the body's type for Stripe and generic, the X-GitHub-Event header for GitHub.
  3. 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

StatusWhen
202Accepted and published.
400The body is not JSON, or not a JSON object.
401The signature did not verify — missing, malformed, wrong secret or outside the replay window. One answer for all of them, on purpose.
402The workspace has used its monthly delivery allowance.
404No such source, or the source is disabled.
413The request is over 512 KiB, or the payload over 256 KiB.
422The type is not registered and the event type policy is Strict.
429Too 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:

Shell
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