SolidHooksSolidHooks/ Docs

Sources

What a source is, how its ingest URL works, and how to create, configure, disable and delete one.

A source is an inbound webhook endpoint. Each source has its own ingest URL, and that URL is all a webhook provider needs to send you events. Sources can also verify that a request really came from the provider before anything is stored or delivered.

Sources live inside a project. Everything a source receives becomes an event, and its connections decide where those events go.

How it works

Creating a source gives you a maskId and an ingestUrl:

https://sdhk.events/<maskId>

The provider's payload is accepted raw, as it sends it, with any content type and no wrapper object. The maskId is the credential: there is no other auth on the ingest URL.

The ingest URL is served from its own origin, separate from the management API. Posting a webhook to the API host returns 404.

Source ids start with src_. The provider you choose decides how the source verifies requests and which event type it reads by default.

Providers

ProviderWhat it is
github, linear, shopify, nuvion, paystackHMAC presets.
stripeStripe signature preset.
openai, anthropic, resendStandard Webhooks presets.
genericAny other sender. You choose the verifier, or none.
emailReceives inbound email instead of HTTP webhooks. See Email ingestion.

For a preset provider, the provider fixes the whole signing scheme. You send only the secret.

Verification at a glance

Name a preset provider and send only its secret:

curl -s "$API/v1/sources" \
  -H "authorization: Bearer $SDHK_KEY" \
  -H 'content-type: application/json' \
  -d '{
    "name": "GitHub events",
    "provider": "github",
    "verifierConfig": { "secret": "<the-webhook-secret-you-set-in-github>" }
  }'

verifierType and every scheme detail are filled in for you. A scheme value that differs from the preset is rejected, not ignored. Use provider: "generic" when you need full control.

Leave out the secret and verifierType (with an empty verifierConfig) and the source accepts everything unverified. A config that carries a secret SolidHooks cannot match to a verifier is rejected rather than stored unverified.

A request that fails verification gets an opaque 401, and is stored as a rejected event so you can see why. For every verifier type, replay windows and the generic HMAC options, see Verification.

Per-source settings

FieldWhat it does
nameHuman-readable name, up to 100 characters.
descriptionOptional free text, up to 500 characters.
maxPayloadSizeKbLargest payload accepted, from 1 to 4096 KB. Defaults to 256.
idempotencyKeysDot-paths such as headers.x-webhook-id or body.data.id used to deduplicate. Rejected on email sources.
eventTypeFieldDot-path to the event type, headers.<name> or body.<path>. Connection filters match on it.
customResponseBody, customResponseContentTypeA static response returned to the sender. Intended for generic sources.
rateLimitRead-only. Requests per second, 0 means unlimited.

eventTypeField can be set on any provider. Known providers default it to their standard path, for example headers.x-github-event for GitHub and body.type for the three Standard Webhooks presets. Omitting it on create, or sending an empty string on update, resets it to the provider's default (null for generic).

rateLimit is an operational clamp that SolidHooks support sets. It is not writable through the API.

Some presets default idempotencyKeys when you send none. openai and anthropic use ["headers.webhook-id"], and resend uses ["headers.svix-id"]. Anything you send, including an empty array, wins.

Create a source

curl -s "$API/v1/sources" \
  -H "authorization: Bearer $SDHK_KEY" \
  -H 'content-type: application/json' \
  -d '{
    "name": "Scratch",
    "provider": "generic",
    "verifierConfig": {}
  }'

The response includes id, maskId and ingestUrl. Configure ingestUrl in the webhook provider.

Update a source

curl -s -X PATCH "$API/v1/sources/$SOURCE_ID" \
  -H "authorization: Bearer $SDHK_KEY" \
  -H 'content-type: application/json' \
  -d '{ "data": { "name": "renamed" } }'

The fields go inside data. Send only the ones you are changing; an empty data object is a valid no-op.

verifierType and verifierConfig travel together. To touch verification, including rotating a secret, send both. A verifierConfig on its own is rejected.

Changing provider or verifierType needs a new credential, because a credential belongs to one provider under one scheme. Send the field the new verifier reads (secret, password or header_value), or set verifierType: "noop" to turn verification off.

To change a secret without an outage, use secret rotation instead.

Rotate the mask

POST /v1/sources/{id}/rotate-mask gives the source a new maskId and so a new ingest URL. The old URL stops working. For an email source the email address changes too, because it is derived from the maskId.

The dashboard has no control for this yet, so it is an API call. It needs a key with source set to read and write.

Disable or enable

POST /v1/sources/{id}/toggle-disabled takes { "isDisabled": true } or false. A disabled source rejects all incoming webhooks. Nothing is deleted, and enabling it again resumes processing.

In the dashboard, use the Disable and Enable buttons in the source's header.

Delete a source

DELETE /v1/sources/{id} removes the source. In the dashboard, the delete confirmation warns that the source and all its connections are removed, and that this cannot be undone.

Deleting needs the delete permission: an organisation admin or owner, or an API key with source set to read and write. See API keys.

The URL is the credential

Anyone who has the ingest URL can post to it. On a source with no verification, that means anyone can create events. Turn verification on where the provider supports it.

Next steps