VISWOO DOCUMENTATION
Webhooks
Receive screen, media and approval events on your own server; signature verification, retries and delivery log.
A webhook is a signed POST request to your server when something happens in Viswoo. Addresses are managed in the
panel under Add-ons → API access or through the API (webhooks:manage). At most 5 addresses per business; the
address must be https, and requests are never sent to internal network addresses.
Events
Section titled “Events”| Event | When | data |
|---|---|---|
screen.offline |
A screen hasn’t been seen for a while (same time as the panel’s offline email) | { screenId, screenName, lastSeenAt } |
screen.online |
An offline screen reconnected | { screenId, screenName, lastSeenAt } |
media.processed |
Uploaded media was processed and can be used | { mediaId, name, type, status, failureCode } |
media.failed |
Media could not be processed | { mediaId, name, type, status, failureCode } |
approval.requested |
An editor’s change awaits approval (Content approval) | { changeId, kind, playlistId, title, requestedBy } |
approval.decided |
An approval request was approved or rejected | { changeId, kind, playlistId, title, status, decidedBy, note } |
webhook.test |
“Send test” from the panel or the API | { message } |
Body:
{ "id": "0195…", "event": "media.processed", "createdAt": "2026-11-01T09:30:00Z", "data": { "mediaId": "…", "name": "summer-sale", "type": "image", "status": "ready", "failureCode": null }}id belongs to the event: the same event goes to every address with the same id and doesn’t change on retries.
Use id to drop duplicates.
Signature
Section titled “Signature”Every request carries an X-Viswoo-Signature: t=<unix seconds>,v1=<hex> header:
v1 = HMAC-SHA256(secret, "<t>.<raw body>")The secret is per address (whsec_…) and is shown only once, when the address is created or the secret rotated.
Your server should compute the signature over the raw body before parsing it, compare in constant time and reject
a t older than 5 minutes.
Node.js
Section titled “Node.js”import { createHmac, timingSafeEqual } from 'node:crypto';
export function verify(secret, header, rawBody) { const parts = Object.fromEntries( String(header ?? '') .split(',') .map((part) => part.split('=')), ); const t = Number(parts.t); if (!parts.v1 || !Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > 300) return false; const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest(); const given = Buffer.from(parts.v1, 'hex'); return given.length === expected.length && timingSafeEqual(given, expected);}using System.Security.Cryptography;using System.Text;
static bool Verify(string secret, string? header, string rawBody){ var parts = (header ?? "").Split(',').Select(p => p.Split('=', 2)).Where(p => p.Length == 2) .ToDictionary(p => p[0], p => p[1]); if (!parts.TryGetValue("t", out var t) || !parts.TryGetValue("v1", out var v1) || !long.TryParse(t, out var unix) || Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - unix) > 300) { return false; }
var expected = HMACSHA256.HashData(Encoding.UTF8.GetBytes(secret), Encoding.UTF8.GetBytes($"{t}.{rawBody}")); return CryptographicOperations.FixedTimeEquals(expected, Convert.FromHexString(v1));}Delivery and retries
Section titled “Delivery and retries”- Answer
2xxwithin 10 seconds; queue the work and respond right away. - A non-
2xxanswer or a connection error is retried after 1 min, 5 min, 30 min, 2 h, 6 h and 15 h (about 24 hours in total). - An address that fails 50 times in a row is paused and the account’s admins get an email. Fix the problem and turn
it back on in the panel or with
PUT /webhooks/{id}(enabled: true); events from the paused period aren’t resent. - Deliveries of the last 7 days are shown in the panel and by
GET /webhooks/{id}/deliveries. - Rotate the secret with
POST /webhooks/{id}/rotate-secret; the old secret stops working immediately.