vlayervlayer docs
Engagement verification

Webhooks

Register a webhook for your brand, verify the HMAC-SHA256 signature, and handle the three events.

Webhooks report the outcome of engagement verification attempts, which is available on request; data lookups need no webhook. They are registered per brand. Each delivery is a POST with a JSON body signed with HMAC-SHA256 using the secret returned when you created the webhook.

Register a webhook

POST/admin/brands/{brandId}/webhooks
curl -X POST {API_BASE_URL}/admin/brands/1/webhooks \
  -H "Authorization: Bearer <API_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/webhooks/engagement",
    "events": ["creator.verified", "reward.issued", "verification.failed"],
    "description": "Production webhook"
  }'
{
  "data": {
    "id": 1,
    "url": "https://example.com/webhooks/engagement",
    "events": ["creator.verified", "reward.issued", "verification.failed"],
    "active": true,
    "description": "Production webhook",
    "lastTriggeredAt": null,
    "createdAt": "2026-06-01T09:00:00.000Z",
    "secret": "whsec_a1b2c3…"
  }
}

The secret is shown once

secret is returned only in this 201 response. Store it now. GET, PATCH, and DELETE on /admin/brands/{brandId}/webhooks/{webhookId} never return it; if it is lost, delete the webhook and create a new one.

events must contain at least one of the three event names. PATCH accepts url, events, description, and active; set active: false to pause deliveries without deleting.

Delivery

Every delivery carries these headers:

HeaderValue
Content-Typeapplication/json
X-Vlayer-EventThe event name, for example creator.verified
X-Vlayer-SignatureBase64 HMAC-SHA256 of the raw request body, keyed with your whsec_* secret

Deprecated headers

Deliveries also carry X-TYB-Event and X-TYB-Signature, with the same values as the X-Vlayer- headers. They will be removed no earlier than December 31, 2026, so read the X-Vlayer- headers in new receivers and switch existing ones before then.

Body:

{
  "event": "reward.issued",
  "timestamp": "2026-06-02T15:05:02.000Z",
  "data": {
    "challengeId": 1,
    "userId": 42,
    "externalUserId": "cust_abc123",
    "username": "creator_jane",
    "platform": "instagram",
    "campaignTitle": "Summer Challenge 2026",
    "attemptId": 39,
    "points": 100
  }
}

Respond with any 2xx within 10 seconds. Other responses and timeouts are retried up to five times with exponential backoff; after that you can replay with the redeliver route.

Verify the signature

Compute the HMAC over the raw body bytes, before JSON parsing, and compare with a constant-time function.

import { createHmac, timingSafeEqual } from "node:crypto";

export function verifySignature(rawBody: Buffer, header: string | undefined, secret: string): boolean {
  if (!header) return false;
  const expected = createHmac("sha256", secret).update(rawBody).digest("base64");
  const received = Buffer.from(header);
  const wanted = Buffer.from(expected);
  return received.length === wanted.length && timingSafeEqual(received, wanted);
}
// Express: keep the raw body for the signature check
app.post("/webhooks/engagement", express.raw({ type: "application/json" }), (req, res) => {
  if (!verifySignature(req.body, req.header("X-Vlayer-Signature"), process.env.ENGAGEMENT_WEBHOOK_SECRET!)) {
    return res.status(401).end();
  }
  const { event, data } = JSON.parse(req.body.toString("utf8"));
  // handle event …
  res.status(204).end();
});

Events

EventWhenExtra fields in data
creator.verifiedA comment or like matched, or an admin force-verified the attemptcommentText (comment challenges)
reward.issuedPoints were issued for a verified attemptpoints
verification.failedVerification did not find a matchreason: retryable (the creator can claim again) or terminal (no further retries)

Fields present on every event:

FieldTypeNotes
challengeIdinteger
userIdintegerInternal creator id
externalUserIdstringYour externalId for the creator. Omitted when not set
usernamestringThe creator's linked handle
platforminstagram or tiktok
campaignTitlestring
attemptIdinteger

Deliveries can arrive more than once. Key your handling on attemptId + event so a replay is a no-op.

Retries are per event, not per endpoint. If your brand has more than one active webhook subscribed to the same event and any one of them fails, the whole delivery is retried and every subscribed endpoint receives the event again, including those that already returned 2xx.

For Vouch web-proof webhooks, which use a different signature scheme, see Verifying Vouch webhooks (opens in a new tab).

Last updated on