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 }
end
function 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 < 300

Any 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.

📘

NOTE

The code below is an example; adapt it to fit your requirements.

(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: 12125551234

If 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.

EventDescription
web_call:connectingFired when the voice call is being initialized.
web_call:progressFired when the call gets a ringing signal from the destination (optional).
web_call:acceptedFired when the receiving party answers the call.
web_call:failedFired if the call fails. The e.detail.cause field holds the reason, for example, User Denied Media Access, Busy, or Canceled.
web_call:endedFired when the call ends.
web_call:media_errorFired when the microphone isn't available. The e.detail.name field holds NotAllowedError, NotFoundError, NotReadableError, or NotSupportedError.
web_call:audio_blockedFired when the browser blocks call audio playback. Ask the visitor to tap a button that calls window.CommPeakWebCallWidget.remoteAudio.play().
web_call:mutedFired 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()
});

Did this page help you?