Webhook Signature Verification

On this page
  1. Webhook signing is optional by default
  2. When to require signatures
  3. Where to find the signing secret
  4. Signature format
  5. Copy-paste examples
  6. Troubleshooting

Webhook signing is optional by default

Webhook workflows accept unsigned inbound posts by default. A new or existing webhook workflow with no explicit setting uses signature_required: false, so tools like Zapier, HighLevel, and custom forms can post to the workflow URL without adding a signature.

When to require signatures

Turn on signing when the sender can compute an HMAC signature for every request. In the workflow builder, open the Webhook trigger settings and enable the signature requirement. Through the MCP tools, update the workflow with trigger_config.signature_required: true using workflow_update. Set it back to false to accept unsigned posts again.

Where to find the signing secret

Each webhook workflow has its own signing_secret. The secret is shown next to the Webhook URL in the workflow builder's Webhook trigger settings after the workflow has been created. Keep it private and only share it with the system that sends requests to that workflow.

Signature format

Send the signature in the x-uphex-signature header. The value must be the lowercase hex HMAC-SHA256 digest of the raw request body, using the workflow's signing_secret as the key. The header may be sent as either the plain hex digest or with a sha256= prefix.

x-uphex-signature: sha256=<lowercase_hex_hmac_sha256>

Sign the exact raw body bytes you send over HTTP. Do not reformat JSON, add whitespace, change key order, or sign a parsed object after computing the digest.

Copy-paste examples

With OpenSSL:

BODY='{"email":"jane@example.com","first_name":"Jane"}'
SECRET='replace-with-workflow-signing-secret'
SIGNATURE=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -binary | xxd -p -c 256)

curl -X POST "$WEBHOOK_URL"   -H "Content-Type: application/json"   -H "x-uphex-signature: sha256=$SIGNATURE"   --data "$BODY"

With Node.js:

import crypto from 'node:crypto';

const body = JSON.stringify({ email: 'jane@example.com', first_name: 'Jane' });
const secret = process.env.UPHEX_WEBHOOK_SIGNING_SECRET;
const signature = crypto
  .createHmac('sha256', secret)
  .update(body)
  .digest('hex');

await fetch(process.env.UPHEX_WEBHOOK_URL, {
  method: 'POST',
  headers: {
    'content-type': 'application/json',
    'x-uphex-signature': `sha256=${signature}`,
  },
  body,
});

Troubleshooting

If a signed request is rejected, confirm the workflow has trigger_config.signature_required: true, the sender is using the matching workflow signing_secret, and the digest was computed from the raw request body. If the sender cannot sign requests, leave signature_required set to false.