Securing a Webhook Trigger with HMAC Signing
How Spojit uses HMAC verification to confirm that incoming requests to a Webhook trigger really came from the sender you expect, which signing schemes are available, and how to add opt-in deduplication with an event-id header.
Overview
A Webhook trigger gives your workflow a public URL that any external system can call with an HTTP POST. That convenience is also a risk: a public URL can be hit by anyone who learns it, so Spojit verifies every incoming request with HMAC (hash-based message authentication code) before it ever starts a run. The sender computes a signature over the request body using a shared secret, sends that signature in a header, and Spojit recomputes the same signature on its side. If the two match, the request is genuine and the workflow runs. If they do not, the request is rejected and no run is created.
In Spojit the secret and the signing rules live in a signing connection, not in the workflow itself. You create the signing connection once, choose a scheme that matches what the sending system produces, and then point your Webhook trigger at it. This keeps secrets out of the canvas and lets you reuse the same verification setup across multiple workflows. The scheme you pick tells Spojit which header to read and how the sender formats its signature, so the same Webhook trigger can accept signed calls from Shopify, GitHub, Slack, your own backend, or a custom system.
How HMAC Verification Protects a Webhook
When a request arrives at a Webhook trigger's URL, Spojit reads the raw request body and the signature header named by the chosen scheme. Using the shared secret stored in the signing connection, it computes its own HMAC over the body and compares it to the value the sender supplied. Only an exact match is accepted. Because the signature is derived from both the secret and the full body, an attacker who does not hold the secret cannot forge a valid signature, and any tampering with the payload in transit changes the computed signature and fails verification.
A request that passes verification is parsed and the workflow runs. The trigger output is the parsed JSON body, available downstream as {{ input }}; if the body is not JSON, Spojit hands it through as { raw: "..." } instead. A verified, accepted request returns 202 with an executionId so the caller knows the run was queued. A request that fails verification never starts a run.
Signing Schemes
The signing connection's Scheme determines which header Spojit reads and how it expects the signature to be encoded. Pick the one that matches the system calling your webhook:
- Spojit: the default scheme for requests you sign yourself. Use this when your own backend or script calls the webhook and you control how the signature is generated. It is the simplest choice when there is no third-party format to match.
- Shopify: matches the signature header and encoding that Shopify uses for its webhooks, so you can verify Shopify notifications without any reformatting.
- GitHub: matches GitHub's webhook signature format. Choose this when a GitHub repository or organization is the sender.
- Slack: matches Slack's request-signing format for events and interactivity payloads.
- Custom: for any sender whose format does not match the built-in schemes. You define which header carries the signature and how it is encoded, then store the shared secret in the connection.
Whichever scheme you choose, the shared secret stays inside the signing connection. The sender must use that same secret to compute its signature, so the secret has to be configured on both ends.
Steps
Follow these steps to put HMAC verification in front of a Webhook trigger.
- Step 1: Create a signing connection. Go to Connections, choose Add connection, and add a webhook signing connection. Pick the Scheme that matches your sender (Spojit, Shopify, GitHub, Slack, or Custom) and store the shared secret. For a Custom scheme, also set the signature header name and encoding the sender uses.
- Step 2: Add a Webhook trigger. In the Workflow Designer, open your workflow's Trigger node and set Trigger Type to Webhook. The trigger exposes the URL that external systems will POST to.
- Step 3: Attach the signing connection. In the trigger's properties, select the signing connection you created. From now on Spojit verifies every incoming request against that connection before starting a run.
- Step 4: Configure the sender. Give the sending system the webhook URL and the same shared secret, and make sure it signs the request body with the matching scheme. Built-in providers like Shopify, GitHub, and Slack do this automatically once you paste the secret into their webhook settings.
- Step 5: Send a test request and confirm acceptance. Trigger the sender (or post a correctly signed body yourself). A verified request returns
202with anexecutionId, and you will see the run appear in execution history with the parsed body under{{ input }}.
Opt-in Deduplication with an Event-id Header
Senders sometimes deliver the same event more than once, for example after a network retry, which can cause a workflow to run twice for one real event. To guard against this, the Webhook trigger supports opt-in deduplication keyed on an event-id header. When you enable it and have the sender include a unique identifier for each event in that header, Spojit treats the first request carrying a given id as the real one and ignores later requests that repeat the same id.
Deduplication is opt-in: if you do not enable it, every verified request starts a run. Turn it on when the sending system can supply a stable per-event identifier and you need exactly-once handling for downstream actions such as creating an order or charging a customer. If your sender does not provide a unique id per event, leave deduplication off and handle idempotency in the workflow itself instead.
Tips
- Verification runs on the exact bytes of the request body, so anything that reformats the payload before it reaches Spojit (a proxy that re-encodes JSON, for example) will change the computed signature and cause a mismatch. Send the raw signed body straight to the webhook URL.
- Use a separate signing connection per sender or per environment. Reusing one secret everywhere means rotating it affects every integration at once.
- Pick the named scheme (Shopify, GitHub, Slack) over Custom whenever the sender is one of those services. The built-in schemes already know the header and encoding, so there is less to get wrong.
- If you need to reply synchronously to the caller rather than just returning
202, add a Response node so the workflow can return a value to the webhook caller.
Common Pitfalls
- Mismatched secret. The most common cause of rejected requests is a secret that differs between the sending system and the signing connection. Re-paste it on both sides to be sure they match exactly, with no trailing whitespace.
- Wrong scheme. Choosing the Spojit scheme for a Shopify or GitHub sender (or the reverse) makes Spojit look in the wrong header or expect the wrong encoding, so valid requests fail. Match the scheme to the actual sender.
- Expecting dedup without an event id. Deduplication only works when the sender supplies a unique identifier per event in the configured header. If that header is missing or repeats values across different events, dedup will not behave as expected.
- Assuming a non-JSON body is parsed. If the sender posts something other than JSON, the body arrives as
{ raw: "..." }rather than parsed fields, so downstream references like{{ input.someField }}will be empty. Check the body format if your variables are blank.
Related Articles
- Setting Up a Webhook Trigger
- Setting Up a Webhook Connection
- Using Response Nodes
- How to Build a Webhook-Triggered Order Processing Workflow
- Troubleshooting Connection Issues