Generic webhook: events, payload and signature
Receive a JSON POST on your server for each event you choose, signed with a secret only you and VSL Max know.
The generic webhook sends a JSON POST to your server every time one of the events you picked happens. It is how you connect VSL Max to an ERP, a members area or any system of your own.
How to add an endpoint
- Open Settings › Integrations and, on the Generic webhook card, click Connect.
- Under Add endpoint, enter the endpoint URL. It must be
https, with no username or password in the URL, on the default port, 443 or 8443, and point to a public address. Local or private network addresses are rejected. - Pick the events and click Add endpoint.
- Copy the signing secret shown right away. It starts with
whsec_and is not shown again: afterwards the screen only shows the last characters. If you lose it, generate a new one.
Only admins create and edit endpoints. An account can have up to 10 endpoints.
Available events
sale.approved: sale approved.sale.refunded: refund.sale.chargeback: chargeback.sale.canceled: sale canceled.video.ready: a video finished processing and is ready.balance.low: the low play balance alert fired (same trigger as the email).organization.suspended: the account was suspended.
If you pick nothing, the endpoint receives the four sale events. The Test button sends a test.ping.
Request format
Every delivery carries these headers:
Content-Type: application/jsonandUser-Agent: VSLMax-Webhooks/1.0.X-VSLMax-Event: the event type, same as thetypefield in the body.X-VSLMax-Delivery: the delivery id, the same across all attempts.X-VSLMax-Signature: in the formatt=<unix time>,v1=<signature>.
Example body for an approved sale:
{
"id": "evt_3f2a9c1e-8b7d-4e21-9a55-0c1d2e3f4a5b",
"type": "sale.approved",
"createdAt": "2026-09-23T15:04:05.000Z",
"data": {
"sale": {
"id": "3f2a9c1e-8b7d-4e21-9a55-0c1d2e3f4a5b",
"externalId": "HP123456",
"status": "approved",
"amount": 197,
"amountCents": 19700,
"currency": "USD",
"occurredAt": "2026-09-23T15:03:58.000Z"
},
"conversion": { "id": "…", "name": "Main Hotmart", "platform": "hotmart" },
"video": { "id": "…", "title": "VSL v3" },
"attribution": "key",
"visitorId": "…"
}
}
For refunds and chargebacks, amount and amountCents are negative. video is null when the sale was not attributed to any video, and attribution tells how it was tied to the video (none when it wasn't).
For the other events, data contains:
video.ready:videowithid,titleanddurationSeconds.balance.low:balancewith the alert, included, used and remaining plays, and the start and end of the cycle.organization.suspended:organizationwithid,statusand the reason.test.ping: a test message.
How to verify the signature
Check the signature before trusting the body:
- Split
tandv1out of theX-VSLMax-Signatureheader. - Compute the HMAC-SHA256, with your secret, of the text made of
t, a dot and the raw request body (before parsing the JSON). - Compare the hex result with
v1using a constant-time comparison. - Reject it if
tis more than 5 minutes away from your clock. That prevents a captured request from being replayed. - Use the event
idto ignore repeats: a retry sends the same id.
Node.js example:
import crypto from 'node:crypto'
function isValidSignature(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')))
const t = Number(parts.t)
if (Math.abs(Date.now() / 1000 - t) > 300) return false
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex')
const a = Buffer.from(expected)
const b = Buffer.from(parts.v1 ?? '')
return a.length === b.length && crypto.timingSafeEqual(a, b)
}
Responses and retries
Reply with any 2xx status within 10 seconds. Any other response, or no response, counts as a failure. Redirects are not followed.
After a failure we retry after 1 minute, 5 minutes and then every 15 minutes, up to 6 attempts in total. If all of them fail, the delivery is marked as failed in the endpoint history and the card shows how many failures happened in a row.
Pause, test and rotate the secret
- Test sends a
test.pingright away and shows whether it was delivered, the HTTP status and the response time. - Latest deliveries lists the endpoint's recent deliveries, with status and number of attempts.
- Pause stops deliveries without deleting the endpoint. Resume starts sending new events again.
- Generate new secret replaces the secret immediately: the next deliveries are already signed with the new one. Update your server right after.
- Delete endpoint asks for your password and stops deliveries at once. It can't be undone.