VISWOO DOCUMENTATION
Your own server
Signed webhook events, signature verification examples (Node, .NET, PHP) and the install token.
When the built-in functions are not enough (connecting to an external system, your own logic), the plugin gets its
own server. In the manifest, server and the server permission:
"permissions": ["settings", "data", "server"],"server": { "webhookUrl": "https://plugin.example.com/viswoo/webhook", "events": ["installed", "settings.changed", "action"]}The address must be https; addresses pointing to internal networks are rejected. Without events every event except
event is sent; list the frequent event explicitly to receive it.
Events
Section titled “Events”The platform POSTs every event with a JSON body:
{ "id": "…", "type": "action", "code": "plugin-code", "tenantId": "…", "createdAt": "2026-11-01T10:00:00Z", "data": {} }| Event | data |
Notes |
|---|---|---|
installed |
{ token, version, permissions } |
A new token every time the customer approves permissions; the old one stops working |
settings.changed |
{ settings } |
Server-only fields included |
uninstalled |
{} |
The customer turned the plugin off (right away or at the end of the period). The install token no longer works; delete data according to your retention policy |
event |
{ source, screenId?, events: [{ name, count }] } |
Sent in batches as the screen or phone reports events with report. Only when listed explicitly in events |
action |
{ action, source, screenId?, payload } |
source: player or public. Synchronous: the JSON body returned within 5 s is the result of call |
Events other than action are retried; deduplicate with id. For action, a non-2xx response makes call fail with
plugin.server_error, no response within 5 s with plugin.server_timeout.
Tokens are per customer: store them by tenantId.
Signature
Section titled “Signature”Every request carries X-Viswoo-Signature: t=<unix seconds>,v1=<hex>:
v1 = HMAC-SHA256(secret, "<t>.<raw body>")The secret is per plugin (whsec_…) and issued by Viswoo. Your server:
- Computes the signature over the raw body, before parsing it.
- Compares in constant time.
- Rejects a
tmore than 5 minutes in the past or future.
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)) return false; if (!long.TryParse(t, out var time) || Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - time) > 300) return false; var expected = HMACSHA256.HashData(Encoding.UTF8.GetBytes(secret), Encoding.UTF8.GetBytes($"{t}.{rawBody}")); byte[] given; try { given = Convert.FromHexString(v1); } catch (FormatException) { return false; } return CryptographicOperations.FixedTimeEquals(given, expected);}function viswoo_verify(string $secret, ?string $header, string $rawBody): bool { parse_str(str_replace(',', '&', $header ?? ''), $parts); if (!isset($parts['t'], $parts['v1']) || !ctype_digit($parts['t'])) return false; if (abs(time() - (int) $parts['t']) > 300) return false; $expected = hash_hmac('sha256', $parts['t'] . '.' . $rawBody, $secret); return hash_equals($expected, $parts['v1']);}
$raw = file_get_contents('php://input');if (!viswoo_verify(getenv('VISWOO_WEBHOOK_SECRET'), $_SERVER['HTTP_X_VISWOO_SIGNATURE'] ?? null, $raw)) { http_response_code(401); exit;}Example flow
Section titled “Example flow”The server template contains a complete Node server: it verifies signatures, takes the token from installed,
answers action and writes data through the platform API.
npx @viswoo/plugin-sdk init quote-of-the-day --template servercd quote-of-the-day && npm installnpx viswoo-plugin dev . --webhook http://localhost:8787/webhooknpm run serverThe simulator prints the signing secret and token on start; during development the template server reads the secret from the simulator’s state file.