Skip to main content

Authenticate your webhook

Configure HMAC, bearer, or custom-header authentication on your webhook trigger so only signed requests pass validation.

Goal

Stop accepting unauthenticated requests on your webhook URL. By the end of this guide, your trigger will reject any request that doesn't carry the right credential and your real provider will sign requests in a way TaskJuice can verify.

When to use this guide

You should authenticate every webhook trigger before pointing real traffic at it. The unauthenticated default is fine for the quickstart and for prototyping with curl, but it is not safe for production:

  • The webhook URL is the only credential and it leaks easily (logs, screenshots, browser history, copy-paste mistakes)
  • Anyone who learns the URL can trigger your workflow as many times as they want
  • An attacker who guesses or scrapes the URL can flood the workflow with garbage events

Authentication adds a second secret that the caller has to know — either a token, a header value, or a signing key. Without it, requests are rejected before any workflow code runs.

Choose an authentication mode

The webhook trigger supports five authentication modes. Pick the one that matches what your provider sends:

ModeUse this whenGenerated secret
noneInternal-only webhooks where the URL is the only credential and the network path is trustedNothing
bearerYour provider supports Authorization: Bearer <token> (most modern APIs and first-party services)A bearer token, displayed in the trigger panel
headerYour provider sends a custom header like x-api-key: <secret> (some legacy APIs)A header secret, displayed in the trigger panel
hmac-sha256Your provider signs the raw body with HMAC-SHA256 (GitHub, Stripe, Slack, Shopify)A signing secret, displayed in the trigger panel
hmac-sha512Your provider signs the raw body with HMAC-SHA512 (rare, only when the provider explicitly requires it)A signing secret, displayed in the trigger panel

HMAC modes are not available when the trigger only allows GET because there is no body to sign. If your provider sends GET requests, choose bearer or header instead.

Open the authentication settings

In the workflow editor, click the webhook trigger node to open the configuration panel on the right. The Authentication Type dropdown is the first field — select your chosen mode from the list.

Some modes generate a secret automatically and display it in a separate field below the dropdown. Use the eye icon to reveal the secret and the copy button to put it on your clipboard. You'll paste this secret into your provider's configuration in the next step.

Configure bearer token authentication

Bearer tokens are the simplest secured option. Pick bearer, copy the generated token, and configure your provider to send it in the Authorization header.

A signed request looks like this:

curl -X POST https://api.taskjuice.ai/webhooks/<your-token> \
  -H "Authorization: Bearer abc123def456ghi789jkl012mno345pq" \
  -H "Content-Type: application/json" \
  -d '{"event":"test"}'

The trigger panel shows the same curl command pre-filled with your actual URL and token — copy it from the Curl Command field to test.

Configure custom header authentication

Some providers send the secret in a custom header instead of the standard Authorization header. Pick header, then enter the exact header name your provider uses in the Header Name field that appears below the dropdown.

Common header names: x-api-key, x-webhook-secret, x-shared-secret. The header name must contain only alphanumeric characters and hyphens.

A signed request looks like this:

curl -X POST https://api.taskjuice.ai/webhooks/<your-token> \
  -H "x-api-key: abc123def456ghi789jkl012mno345pq" \
  -H "Content-Type: application/json" \
  -d '{"event":"test"}'

Configure HMAC-SHA256 authentication

HMAC is the right choice for any provider that signs requests with a shared secret — GitHub, Stripe, Slack, Shopify, and most provider-to-platform integrations use it.

Pick hmac-sha256 (or hmac-sha512 if your provider requires SHA-512) and copy the generated signing secret from the trigger panel. Configure your provider to:

  1. Compute HMAC(rawBody, secret) using SHA-256
  2. Send the resulting hex-encoded digest in a signature header
  3. Send the digest as either sha256=<hex> or just <hex> (depending on the provider's convention)

A signed request looks like this:

curl -X POST https://api.taskjuice.ai/webhooks/<your-token> \
  -H "x-hub-signature-256: sha256=5257a869e7ecebeda32affa62cdca3ec6e9d7b0d5cba0f0a8b3e9d2a3f4c5b6a" \
  -H "Content-Type: application/json" \
  -d '{"event":"test"}'

If you're writing your own caller (not using a provider library), here's how to compute the signature in three common languages:

import crypto from 'node:crypto';

const secret = 'abc123def456ghi789jkl012mno345pq';
const body = JSON.stringify({ event: 'test' });

const signature = crypto
  .createHmac('sha256', secret)
  .update(body)
  .digest('hex');

await fetch('https://api.taskjuice.ai/webhooks/<your-token>', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-hub-signature-256': `sha256=${signature}`,
  },
  body,
});

The critical detail: the signature is computed against the raw request body, byte-for-byte. Any whitespace difference, any reordering of JSON keys, any encoding change between the signer and the verifier will cause the signature to mismatch and the request to return 401. Always send the exact same bytes you signed.

Add replay defense

Replay defense protects against an attacker capturing a valid signed request and replaying it later. Without it, a single compromised request can be sent over and over forever. With it, replayed requests are rejected as soon as the timestamp falls outside a tolerance window.

Replay defense is only available for HMAC modes. Three optional fields appear under the Replay Defense section:

FieldPurposeTypical value
Timestamp HeaderThe header name where the upstream system sends the request timestampX-Timestamp or Stripe-Signature-Timestamp
Tolerance (seconds)How old a request can be before TaskJuice rejects it300 (five minutes)
Nonce HeaderThe header name where the upstream system sends a unique per-request nonceX-Request-Id

You don't have to set all three. The most common configuration is just Timestamp Header and Tolerance — the timestamp prevents replay outside the window, and the nonce header is only needed for higher security postures where even within-window replays must be rejected.

A typical Stripe-style configuration uses Stripe-Signature-Timestamp as the timestamp header with a tolerance of 300 seconds.

Test your auth setup

The easiest way to verify auth is working is to use the auto-generated curl command in the trigger panel. The Curl Command field includes your URL, the chosen auth headers, and a sample body — copy it and run it in a terminal.

A correctly signed request returns:

HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: 7f8e9d0a-1b2c-3d4e-5f60-7a8b9c0d1e2f

{"received":true,"request_id":"7f8e9d0a-1b2c-3d4e-5f60-7a8b9c0d1e2f"}

To verify rejection works, send the same request without the auth header (or with a wrong value):

curl -i -X POST https://api.taskjuice.ai/webhooks/<your-token> \
  -H "Content-Type: application/json" \
  -d '{"event":"test"}'

You should see:

HTTP/1.1 401 Unauthorized
Content-Type: application/json
X-Request-Id: 8a9b0c1d-2e3f-4a5b-6c7d-8e9f0a1b2c3d

{"error":"authentication failed","request_id":"8a9b0c1d-2e3f-4a5b-6c7d-8e9f0a1b2c3d"}

Notice that the error message says only "authentication failed". The same string is returned for every reason auth could fail — wrong secret, missing header, expired timestamp, invalid signature format. This is intentional: a more specific message would tell an attacker which guess was closer to correct.

Troubleshooting

SymptomWhat to check
401 authentication failed on every requestConfirm the secret in your provider's config matches the one in the trigger panel exactly. Whitespace and trailing newlines count.
Bearer auth works in curl but not from your providerConfirm your provider sends the literal string Bearer <token> in the Authorization header, with a single space between them.
Custom header auth fails for one specific providerSome providers normalize header names to lowercase, others preserve case. TaskJuice matches case-insensitively, so this is rarely the cause — but the header value must match exactly.
HMAC works in your test script but not from productionThe most common cause is a body encoding mismatch. Ensure your provider signs the bytes that actually go on the wire — minified JSON, no trailing whitespace, no character set conversion.
HMAC fails intermittentlyCheck if you have replay defense enabled with a low tolerance. A clock skew between your provider and TaskJuice can cause edge-of-window failures. Increase the tolerance or sync your provider's clock.
HMAC signature differs by one characterConfirm the encoding. TaskJuice expects hex by default. Some providers send base64 — check the trigger panel's HMAC encoding setting.
Provider sends GET and the auth dropdown won't let you pick HMACHMAC is only available when the trigger accepts POST. Pick bearer or header instead.
Was this helpful?