Developer cookbook

Realtime events cookbook - Signal WS + server webhooks.

Two complementary event families. Family 1 is the Signal WebSocket (wss://<tenant>/signal.ashx) - low-latency room + call transitions consumed by browser SoftPhones and ops dashboards. Family 2 is server-side HTTP webhooks (POST to your endpoint) registered in webhooks-admin.html - durable, retried, HMAC-signed. European Digital Identity Wallet session events flow through the same webhook dispatcher.

Family 1 - Signal WebSocket

Endpoint: wss://<tenant>/signal.ashx?room=<room>&name=<peer>. No Bearer required - tenant boundary and IpAllowGate protect the socket. Server-emitted event type values verified in live code:

Event typePayload shapeFire conditionStatus
welcome{type,peers[],mode,room}Sent to a joining peer after admission.LIVE
peer-joined{type,peer:{id,name,role}}Broadcast when a new peer enters the room.LIVE
peer-left{type,peer:{id,name}}Broadcast when a peer disconnects.LIVE
mode-changed{type,mode:mesh|sfu}Broadcast when the room flips mesh<->SFU.LIVE
ring{type,from,room}Sent to a callee to trigger ringing UI.LIVE
ring-cancelled{type,reason}Sent when the caller aborts before answer.LIVE
ring-cleared{type}Sent when the room clears an active ring state.LIVE
dial-result{type,ok,error?}Result of a bridge-side dial attempt.LIVE
office-registered{type,user,expires}SoftPhone registration ack.LIVE
office-call-result{type,ok,error?}Result of a SoftPhone outbound-call attempt.LIVE
office-call-answered{type,peer}Callee answered a SoftPhone call.LIVE
office-call-timeout{type,reason}Callee did not answer in time.LIVE
sfu-answer{type,sdp}SFU SDP answer to a publisher offer.LIVE
sfu-subscribe-answer{type,sdp,participantId}SFU SDP answer to a subscribe offer.LIVE
sfu-offer-failed{type,error}SFU publisher offer was rejected.LIVE
sfu-subscribe-failed{type,error}SFU subscribe offer was rejected.LIVE
signal{type,payload}Generic relay frame (SDP/ICE).LIVE
knock{type,peer}Broadcast to admins when a peer knocks.LIVE
knock-pending{type}Sent to a knocker while waiting for admit.LIVE
denied{type,reason?}Sent to a peer whose knock was denied.LIVE
dup-ip-warning{type,ip}Broadcast when two peers share source IP.LIVE
breakout-list{type,rooms[]}Sent when breakout rooms are (re)listed.LIVE
breakout-assigned{type,room}Sent to a peer moved into a breakout.LIVE
breakout-end{type}Broadcast when a breakout ends.LIVE
breakout-rejected{type,reason}Rejection of a breakout request.LIVE
pong{type}Server reply to a client ping.LIVE
error{type,message}Server-side error frame.LIVE

Snippet matrix

LanguageLibraryRecipeSnippet
Browser JSnative WebSocketOpensignal-ws-browser.js
Node.jswsOpensignal-ws-nodejs.js
PythonwebsocketsOpensignal-ws-python.py
Gogorilla/websocketOpensignal-ws-go.go
.NETSystem.Net.WebSocketsOpensignal-ws-dotnet.cs

Browser JS Recipe

native WebSocket. Download raw: signal-ws-browser.js

Node.js Recipe

ws. Download raw: signal-ws-nodejs.js

Python Recipe

websockets. Download raw: signal-ws-python.py

Go Recipe

gorilla/websocket. Download raw: signal-ws-go.go

.NET Recipe

System.Net.WebSockets. Download raw: signal-ws-dotnet.cs

Family 2 - server-side webhooks

The receive side is documented in the webhooks cookbook. Below is the honest event catalog with LIVE vs RESERVED status:

Event namePayload shapeStatus
call.startedcall payloadLIVE
call.answeredcall payloadLIVE
call.endedcall payloadLIVE
call.ice.connectedcall payloadLIVE
call.transferredcall payloadLIVE
transcript.savedtranscript payloadLIVE
outbound-ai.finishedoutbound payloadLIVE
sfu.publisher.connectedpublisher payloadLIVE
sfu.subscriber.connectedsubscriber payloadLIVE
room.cascade.promotedroom payloadLIVE
room.cascade.demotedroom payloadLIVE
room.cascade.failedroom payloadLIVE
room.peer.joinedpeer payloadLIVE
room.peer.leftpeer payloadLIVE
room.bandwidth.degradedroom payloadLIVE
sfu.offer.receivedoffer payloadLIVE
sfu.subscribe.receivedsubscribe payloadLIVE

Auth model

WebSocket family. No Bearer today. The signal WebSocket is tenant-bound by request Host and gated by IpAllowGate; invite tokens (?tok=) are used for admission when a room is locked. Webhook family. Every POST carries X-CodeB-Signature: sha256=<hex>. The verify pattern is documented in the webhooks cookbook.

Reconnection + backoff

All five snippets implement the same pattern: on WS close, wait backoff, then reconnect. Start at 1 second, double on each failure, cap at 30 seconds. Reset on successful open.

var backoff = 1000; ws.on('close', function(){ setTimeout(connect, backoff); backoff = Math.min(30000, backoff * 2); });

RASP posture

Both surfaces enforce server-side controls: per-tenant rate limits at signal.ashx, IpAllowGate before WS accept, algorithm allow-list on HMAC signatures, tenant isolation via Host header, and loud [SIGNAL-*], [WEBHOOK-*], [api-key-diag] diagnostics at every branch. Anomalies are logged inline, not swallowed.

NIS2 / DORA / CRA

Realtime signalling and webhook streams support NIS2 incident-detection duties (near-real-time signal into a SIEM) and DORA operational-resilience monitoring. The CRA product-side auditability requirement is met by tenant-scoped, HMAC-signed webhook delivery and by loud diagnostics on every branch.

FAQ

Structured answers are embedded in the schema block above.