Integrating the WebCall Widget into Your Website
Add the WebCall Widget to your site with a JWT endpoint and an embed snippet, receive callback webhooks, and control calls from JavaScript.
To take calls from your website, the WebCall Widget needs two things: a backend endpoint that signs a JWT, and a script on your page. This page covers both, plus the callback webhook and the JavaScript API for custom integrations. Your website must use HTTPS, because calls need access to the visitor's microphone.
You need the widget's UUID and JWT secret from VoIP Services > WebCall Widgets. To create a widget, see WebCall Widget: In-Page VoIP Calls.
Preparing the JWT generation endpoint
To prevent unauthorized use, CommPeak requires JWT authentication. Implement an HTTP endpoint on your server that issues a signed JWT to authenticate the widget.
The JWT payload must include:
- sub: The UUID of the WebCall Widget, from the WebCall Widgets page.
- exp: The token expiration timestamp. We recommend setting it to 10 seconds.
Implementation examples
You can use any library listed on the official JWT site. The examples below use Ruby and PHP with the SHA256 algorithm for the JWT signature. The response must be JSON, with the signed token in the data attribute.
def webcall_jwt
# UUID of the WebCall Widget and expiration set to 30 seconds from now
payload = { sub: 'bf6da209-6cea-40b8-aa14-6a913286d442', exp: Time.now.to_i + 30 }
# JWT Secret used to sign the token
secret = 'secretSignKey'
# Generate a signed JWT
token = JWT.encode(payload, secret, 'HS256')
# Return the signed JWT within the response body
render json: { data: token }
endfunction generateJwt(): String {
$signingKey = "secretSignKey";
$header = [
"alg" => "HS256",
"typ" => "JWT"
];
$header = base64UrlEncode(json_encode($header));
$payload = [
"exp" => time() + 10,
"sub" => "bf6da209-6cea-40b8-aa14-6a913286d442"
];
$payload = base64UrlEncode(json_encode($payload));
$signature = base64UrlEncode(hash_hmac('sha256', "$header.$payload", $signingKey, true));
$jwt = "$header.$payload.$signature";
return $jwt;
}
function base64UrlEncode($text): String {
return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($text));
}
echo json_encode(["data" => generateJwt()]);Adding the widget to your page
Copy the snippet below into your page's HTML. Replace the placeholders with your WebCall Widget UUID, JWT endpoint URL, and request method:
<script src="https://my.commpeak.com/web_calls/embedded_widget/<UUID>.js"></script>
<script>(function(window, document) { new window.CommPeakWebCallWidget.Widget('<JWT ENDPOINT>', '<JWT METHOD>').init();})(window, document);</script>The snippet adds the widget launcher to your page.
If you place the widget inside an iframe, allow the microphone in the iframe:
<iframe src="..." allow="microphone; autoplay"></iframe>Receiving callback webhooks
If you set a Webhook URL for callback requests (see Receiving callback requests), each callback request arrives as a POST request:
POST https://example.com/webhooks/callbacks
Content-Type: application/json
X-CommPeak-Event: web_call.callback_request
X-CommPeak-Timestamp: 1767225600
X-CommPeak-Signature: sha256=<hex HMAC-SHA256 of "<timestamp>.<raw body>" keyed with your JWT Secret>
{
"event": "web_call.callback_request",
"id": 123,
"widget": { "uuid": "<webcall uuid>", "name": "Sales Department" },
"contact": { "first_name": "John", "last_name": "Doe", "phone": "+12125551234", "email": "[email protected]" },
"requested_at": "2026-02-23T14:00:00Z",
"timezone": "Europe/Berlin",
"page_url": "https://example.com/pricing",
"created_at": "2026-02-20T09:12:44Z"
}Verify the signature before you trust the payload, and reject timestamps older than a few minutes. Example in Ruby:
timestamp = request.headers['X-CommPeak-Timestamp']
expected = 'sha256=' + OpenSSL::HMAC.hexdigest('SHA256', jwt_secret, "#{timestamp}.#{request.raw_post}")
valid = ActiveSupport::SecurityUtils.secure_compare(expected, request.headers['X-CommPeak-Signature'].to_s) &&
(Time.now.to_i - timestamp.to_i).abs < 300Any 2xx response marks the request as delivered. For other responses, CommPeak retries the request a few times. Redirects aren't followed.
Advanced usage: starting a call from JavaScript
If you want the calling features without the CommPeak UI widget, load the call script yourself.
Adding the JavaScript initialization snippet
After setting up your JWT generation endpoint, add the following JavaScript snippet to your webpage. The script asks for microphone access, fetches the JWT from your endpoint, and loads the CommPeak call script.
(function(window, document) {
// Widget initialization code
window.CommPeakWebCallWidget = window.CommPeakWebCallWidget || {};
window.CommPeakWebCallWidget.loadCallScript = function(jwtEndpointUrl, jwtEndpointMethod) {
// request the microphone right away (within the user click), mobile browsers and Firefox may
// hide or block the permission prompt otherwise; the stream is reused by the call script
navigator.mediaDevices.getUserMedia({ audio: true }).then(function(stream) {
window.CommPeakWebCallWidget.localStream = stream;
requestCall(jwtEndpointUrl, jwtEndpointMethod);
}).catch(function(error) {
// error.name: NotAllowedError (denied), NotFoundError (no microphone), NotReadableError (microphone busy)
// show the user how to allow microphone access for your site
});
};
function requestCall(jwtEndpointUrl, jwtEndpointMethod) {
var req = new XMLHttpRequest();
req.responseType = 'json';
req.open(jwtEndpointMethod, jwtEndpointUrl);
req.addEventListener('load', function(_event) {
// in 1st step we are returning JSON response with "data" attribute holding the signed JWT
var token = req.response.data;
// importing the CommPeak WebCall Widget script with JWT token
var scriptElement = document.createElement('script');
scriptElement.src = `https://my.commpeak.com/web_calls/embedded-${token}.js`;
scriptElement.async = true;
scriptElement.defer = true;
(document.head || document.body || document.documentElement).appendChild(scriptElement);
});
req.send();
}
})(window, document);Initiating a call
To start a voice call, call the loadCallScript function from a user action, such as a button click, so the browser shows the microphone prompt:
// Replace parameters with your actual ones
window.CommPeakWebCallWidget.loadCallScript('/webcall_jwt', 'GET');Sending DTMF
To send DTMF tones, use the following code snippet:
// single DTMF
var dtmf = 3;
// multiple DTMF
var dtmf = '123#';
// options (optional) - send as RFC2833
var options = { 'transportType': 'RFC2833' };
// options parameter can be omitted
window.CommPeakWebCallWidget.callInProgress && window.CommPeakWebCallWidget.sendDTMF(dtmf, options)Muting a call
To mute or unmute the microphone during a call, use the following commands:
window.CommPeakWebCallWidget.mute()
window.CommPeakWebCallWidget.unmute()Hanging up a call
To end an ongoing call, use the following command:
window.CommPeakWebCallWidget.callInProgress && window.CommPeakWebCallWidget.hangUp()Passing custom attributes to calls (optional)
To pass custom metadata with the call, set up the window.webCallUserProfile object before you trigger the call. The widget forwards each attribute to the WebCall destination as a SIP header in the X-W-[key]: [value] format.
window.webCallUserProfile = { "oid": "XYZ123", "name": "John Doe", "phone": "12125551234" };
// Will add following headers:
// X-W-oid: XYZ123
// X-W-name: John Doe
// X-W-phone: 12125551234If you pass the phone attribute, the widget uses it as the Caller ID of the call.
Handling JavaScript events (optional)
The WebCall Widget dispatches events on window that let you track the call in your UI. Every event payload includes the UUID of the widget.
| Event | Description |
|---|---|
web_call:connecting | Fired when the voice call is being initialized. |
web_call:progress | Fired when the call gets a ringing signal from the destination (optional). |
web_call:accepted | Fired when the receiving party answers the call. |
web_call:failed | Fired if the call fails. The e.detail.cause field holds the reason, for example, User Denied Media Access, Busy, or Canceled. |
web_call:ended | Fired when the call ends. |
web_call:media_error | Fired when the microphone isn't available. The e.detail.name field holds NotAllowedError, NotFoundError, NotReadableError, or NotSupportedError. |
web_call:audio_blocked | Fired when the browser blocks call audio playback. Ask the visitor to tap a button that calls window.CommPeakWebCallWidget.remoteAudio.play(). |
web_call:muted | Fired when the microphone is muted or unmuted. The e.detail.muted field holds true or false. |
The UI widget handles microphone errors on its own. In custom integrations, listen to the events like this:
window.addEventListener('web_call:failed', function(e) {
// e.detail.cause, e.g. "User Denied Media Access", "Busy", "Canceled"
});
window.addEventListener('web_call:media_error', function(e) {
// e.detail.name: NotAllowedError, NotFoundError, NotReadableError, NotSupportedError
});
window.addEventListener('web_call:audio_blocked', function(e) {
// browser blocked call audio playback, ask the user to tap a button calling
// window.CommPeakWebCallWidget.remoteAudio.play()
});
window.addEventListener('web_call:muted', function(e) {
// e.detail.muted: true / false. Toggle with window.CommPeakWebCallWidget.mute() / .unmute()
});Updated about 7 hours ago