Appearance
Webhook receiver
Receives webhooks (HTTP callbacks other systems send when something happens). Place it right after an Http listener (on its Output pin). The listener owns the URL, port and TLS; this component does the webhook specific work: answers URL checks, checks the signature, drops repeated deliveries and answers the sender quickly.
Pins
| Pin | Kind | Types | Description |
|---|---|---|---|
| Input | input | Viaducta.Plugins.Http.Types.WebHttpRequest | Connect to the Http listener Output pin. |
| Output | output | Viaducta.Plugins.Http.Types.WebHttpRequest | The accepted request. The Webhook metadata holds EventId, EventType, IsVerified. |
| Rejected | error | Viaducta.Plugins.Http.Types.WebHttpRequest | Called after a request failed the signature check (already answered with RejectStatusCode). Webhook.RejectReason says why. Optional. |
Configuration
| Field | Type | Default | Description |
|---|---|---|---|
SignatureScheme | dropdown: 0 None, 1 HmacHeader, 2 GitHub, 3 Stripe, 4 Slack, 5 StandardWebhooks, 6 SharedSecretHeader, 7 MicrosoftGraph | 0 | How the sender signs requests, see below. |
Secret | password | The signing secret. StandardWebhooks: the whsec_... value. SharedSecretHeader: the expected header value. MicrosoftGraph: the subscription clientState. | |
SecondarySecret | password | Also accepted; use while rotating secrets. | |
SignatureHeader | string | X-Signature | HmacHeader / SharedSecretHeader only: the header that carries the signature or secret. |
SignaturePrefix | string | HmacHeader only: removed from the header value before comparing (for example sha256=). | |
HmacAlgorithm | dropdown: 0 Sha256, 1 Sha1, 2 Sha512 | 0 | HmacHeader only. |
SignatureEncoding | dropdown: 0 Hex, 1 Base64 | 0 | HmacHeader only. |
TimestampToleranceSeconds | integer | 300 | Stripe, Slack, StandardWebhooks: max age of the signed timestamp (replay protection). 0 = off. |
AckMode | dropdown: 0 Immediate, 1 AfterFlow | 0 | Immediate: answer the sender right after the checks, then run the flow. AfterFlow: the flow answers with an Http response component. |
AckStatusCode | integer | 200 | Status sent for accepted requests in Immediate mode (2xx). |
RejectStatusCode | integer | 401 | Status sent when the signature check fails. |
HandleHandshakes | boolean | true | Answer URL checks (see below). |
VerifyToken | password | Meta style subscription check token. Empty = off. | |
DeduplicationWindowSeconds | integer | 600 | Deliveries with an event id seen within this window are answered but not passed on. 0 = off. |
ContractCheck | dropdown: 0 Off, 1 Warn, 2 Reject | 1 | What to do when a generated client sends a contract hash (X-Viaducta-Contract) that differs from the payload type's, see "Contract check". |
EventIdHeader | string | Header with the event id. Empty = scheme default. | |
EventTypeHeader | string | Header with the event type. Empty = scheme default. | |
ClientLanguage | code generator (CSHARP, PYTHON, GO, JAVA, PHP) | CSHARP | Designer only: the button opens generated client code that calls this receiver, see "Client code" below. Not used at run time. |
Signature schemes
| Scheme | Sender examples | What is checked |
|---|---|---|
| None | Nothing; every request is accepted (warning at start). | |
| HmacHeader | Shopify (Base64), your own senders | HMAC of the body, in SignatureHeader. |
| GitHub | GitHub, Meta (Facebook, WhatsApp, Instagram) | X-Hub-Signature-256: sha256=<hex> (falls back to X-Hub-Signature sha1). |
| Stripe | Stripe | Stripe-Signature: t=..,v1=.. over t.body, timestamp tolerance. |
| Slack | Slack Events API | X-Slack-Signature: v0=.. over v0:ts:body, timestamp tolerance. |
| StandardWebhooks | Standard Webhooks, Svix based (Clerk, Resend, ...) | webhook-id/-timestamp/-signature (or svix-*), timestamp tolerance. |
| SharedSecretHeader | simple senders | The header value equals the secret. |
| MicrosoftGraph | Microsoft Graph change notifications (Outlook mail, calendar, Teams, ...) | Every value[].clientState equals the secret. |
Event id / type defaults: GitHub X-GitHub-Delivery / X-GitHub-Event; StandardWebhooks webhook-id / body type; Stripe body id / type; Slack body event_id / event.type. Other schemes have no default id, so duplicates are only detected when EventIdHeader is set.
Handshakes (HandleHandshakes)
- Microsoft Graph (scheme MicrosoftGraph): a request with
?validationToken=...is answered200 text/plainwith the token. - Slack (scheme Slack): a signed
{"type":"url_verification"}is answered with itschallenge. - Meta (any scheme, when
VerifyTokenis set):GET ?hub.mode=subscribe&hub.verify_token=..&hub.challenge=..is answered with the challenge, or403for a wrong token.
Handshakes are answered and not passed to Output.
Behaviour
- Handshake? Answer and stop.
- Check the signature against the raw body bytes. On failure: answer
RejectStatusCode, callRejected, stop. The detailed reason is logged, the sender only gets a generic text. - Read event id and type into the
Webhookmetadata. - Event id seen within the window? Answer (2xx) and stop.
- Immediate: answer
AckStatusCodewith an empty body and close the response, then callOutput. AfterFlow: callOutput; the flow must send the answer. If the flow fails the event id is forgotten so the sender's retry is processed.
Client code
The ClientLanguage button opens client code for this receiver in the designer (Download saves it): C# (.NET 8+, no packages), Python (3.10+, standard library only), Go (1.24+, standard library only) Java (17+, Jackson 2.15+ for the payload, one <Payload>WebhookClient.java with the types nested in it) or PHP (8.1+, no packages; decimals are exact strings; HTTPS needs the openssl extension). The same menu gives an OpenAPI 3.1 document (YAML or JSON) of the request clients send (paths): the payload schema, the headers, and the signature in x-viaducta-signature (OpenAPI can't express HMAC signing; SharedSecretHeader is an apiKey security scheme), for tools such as openapi-generator or Postman. It contains the payload types, taken from the Json deserializer after the Output pin (without one the client sends JSON text), and a <Payload>WebhookClient that serializes, signs the request the way SignatureScheme checks it, posts it to the listener's address and retries network errors, timeouts, 408, 429 and 5xx with the same event id (dropped as a duplicate when DeduplicationWindowSeconds is on and the scheme carries the id in a header: StandardWebhooks, GitHub, or EventIdHeader). The secret is read from the constructor or the VIADUCTA_WEBHOOK_SECRET environment variable, never written into the code. MicrosoftGraph receivers get the payload types only.
Contract check
Generated clients send the contract hash of their payload type (a fingerprint of its JSON shape: names, types, nullability, omit rules, enum values) in X-Viaducta-Contract. At start the receiver computes the hash of the type the Json deserializer after its Output pin produces, with the same code. When a client sends a different hash it was generated for an older (or newer) version of the type:
- Warn (default): the delivery is accepted and a warning names both hashes (once per hash).
- Reject: the delivery is answered
409("regenerate the client") and passed toRejectedwithWebhook.RejectReason; it is checked after the signature, so only authenticated senders see it, and the event id is not remembered, so the regenerated client can deliver it again. - Off: not checked.
Requests without the header (other senders, curl) are always accepted. Without a single flow type coming out of a Json deserializer after the receiver nothing is checked. Any change of the JSON shape changes the hash, also an added optional property that old clients can ignore, so Warn is the safe default.
Notes
- Turn on Keep raw body in the Http listener. Signatures are made over the exact bytes; without it the text body is re-encoded as UTF-8, which usually but not always gives the same bytes (warning at first use), and form posts (
application/x-www-form-urlencoded) can't be checked at all. - In Immediate mode an Http response later in the branch fails with "already sent"; errors in the flow are logged and no longer reach the sender. Senders usually wait only 5-10 seconds and retry on timeout, so Immediate is the safe default.
- Duplicate detection is in memory per component: it does not span several hosts or restarts.
- The flow keeps the listener's request slot (
MaxParallelRequests) until it finishes, also in Immediate mode.
Example
Http listener (https://0.0.0.0:8443/hooks/github, Keep raw body on) → Webhook receiver (GitHub, secret) → Switch on Webhook.EventType → ...