VYG Docs
Webhooks

HMAC Verification

How to sign custom integration webhook requests so VYG accepts them.

Sign webhook requests with HMAC-SHA256 using your webhook secret and the exact body bytes you send. An invalid signature returns 401 Unauthorized; correct it before retrying.

HMAC SHA-256 is the default and recommended way to authenticate your webhooks. In the app it is the Signing secret security check. If your platform can't compute an HMAC signature, see Validation Methods for the alternatives (secret header or allowed IP addresses) and how to choose.

The algorithm

  1. Take the exact bytes of the JSON body you are about to send. Do not pretty-print, re-stringify, or normalize whitespace after signing.
  2. Compute HMAC-SHA256(secret, body).
  3. Hex-encode the result, lowercase.
  4. Send it in the x-vyg-signature header.

Sign the bytes you send

Serialize the body once, sign it, and send the same value. Changing whitespace or field order after signing invalidates the signature. Make sure your HTTP client sends the body unchanged.

Worked examples

All three examples below produce the same signature for the same body and secret. You can paste them locally to verify.

Fixture

secret = whsec_demo_secret
body   = {"type":"checkout_abandoned","event_id":"evt_demo_1","data":{"id":"ck_001"}}

expected x-vyg-signature:
  d9eea748ce5eff60b7f4edea5da4488ef750296f9ca6c145ea95b0337e580c6b

cURL + OpenSSL

SECRET='whsec_demo_secret'
BODY='{"type":"checkout_abandoned","event_id":"evt_demo_1","data":{"id":"ck_001"}}'

SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')
echo "$SIG"
# d9eea748ce5eff60b7f4edea5da4488ef750296f9ca6c145ea95b0337e580c6b

curl -sS -X POST "https://<your-vyg-webhook-host>/custom/<webhookId>" \
  -H 'content-type: application/json' \
  -H 'x-vyg-topic: checkout_abandoned' \
  -H 'x-vyg-event-id: evt_demo_1' \
  -H "x-vyg-signature: $SIG" \
  --data "$BODY"

Note the printf '%s' (not echo) — echo adds a trailing newline that would invalidate the signature.

Node.js

import { createHmac } from 'node:crypto';

const secret = 'whsec_demo_secret';
const rawBody = '{"type":"checkout_abandoned","event_id":"evt_demo_1","data":{"id":"ck_001"}}';

const signature = createHmac('sha256', secret).update(rawBody).digest('hex');
// d9eea748ce5eff60b7f4edea5da4488ef750296f9ca6c145ea95b0337e580c6b

await fetch('https://<your-vyg-webhook-host>/custom/<webhookId>', {
	method: 'POST',
	headers: {
		'content-type': 'application/json',
		'x-vyg-topic': 'checkout_abandoned',
		'x-vyg-event-id': 'evt_demo_1',
		'x-vyg-signature': signature,
	},
	body: rawBody,
});

The string passed to .update() and the string passed as body must be the same value. If you build the payload as an object, stringify it once into a const rawBody, sign that, and send that.

Python

import hmac
import hashlib
import urllib.request

secret = b'whsec_demo_secret'
raw_body = b'{"type":"checkout_abandoned","event_id":"evt_demo_1","data":{"id":"ck_001"}}'

signature = hmac.new(secret, raw_body, hashlib.sha256).hexdigest()
# d9eea748ce5eff60b7f4edea5da4488ef750296f9ca6c145ea95b0337e580c6b

req = urllib.request.Request(
    'https://<your-vyg-webhook-host>/custom/<webhookId>',
    data=raw_body,
    method='POST',
    headers={
        'content-type': 'application/json',
        'x-vyg-topic': 'checkout_abandoned',
        'x-vyg-event-id': 'evt_demo_1',
        'x-vyg-signature': signature,
    },
)
urllib.request.urlopen(req)

If you are using requests, build the body with json.dumps(...) into a string variable, sign that string, and pass it as data= (not json=, which would re-serialize and may change the bytes).

Rotating your secret

Open your data source in Settings → Data sources and click Replace signing secret. It is in Connection details, on the Settings tab and in the ⋯ menu. The previous secret stays valid for 24 hours while you update your sender.

After 24 hours, requests signed with the previous secret return 401. Rotating again replaces the previous secret and starts a new 24-hour window.

  1. Click Replace signing secret and copy the new secret. It is shown only once.
  2. Deploy the new secret to your sender. Any time within the next 24 hours is safe, because requests signed with the old secret still verify until then.
  3. After your deploy is live, confirm new requests succeed with 200 OK. The old secret expires on its own; there is nothing to remove.

Replacing the signing secret does not change any API key. Webhook delivery uses the secret; API integrations use a vyg_ba_ key from Settings → Data API Keys.

On this page