Click2Call
The Click2Call microservice exposes a small HTTP/JSON API that originates click-to-call calls without requiring a portal JWT. It is meant to be consumed by a browser extension that detects phone numbers on web pages and, on click, asks the service to place the call.
Unlike the role-based REST APIs, this service does not use JWT authentication: the caller proves ownership of a terminal with the terminal’s own SIP credentials via HTTP Digest, so the password never travels over the wire.
On success the service fires, over AMI, an Originate that reuses the existing
click2dial dialplan:
Leg 1: the user’s own terminal rings.
Leg 2: when the user answers, the call towards the requested
destinationis generated.
Note
This service is shipped as the ivozprovider-click2call package and listens
on its own port (behind a TLS proxy in production). It is independent from the
/api/platform, /api/brand and /api/provider REST APIs.
Endpoints
The flow always has two steps: first request a challenge, then place the call signing the destination with the challenge.
1) POST /challenge
Requests a single-use nonce for an Address of Record (AoR).
Request body:
{ "aor": "username@domain" }
Response (200):
{ "nonce": "<server-issued nonce>", "realm": "domain" }
aor: the terminal’susername@domain.realm(returned): the domain part of the AoR, to be used in the digest.
The nonce is HMAC-signed, has a configurable freshness (nonce_ttl) and is
single-use, so there is no replay. No database access happens at this step.
2) POST /call
Places the call. The destination must be signed with a digest computed from the
terminal password and the nonce obtained above.
Request body:
{
"aor": "username@domain",
"nonce": "<nonce from /challenge>",
"response": "<digest response>",
"destination": "+34900000000",
"iden": "optional-call-id",
"maxDuration": 10800000,
"dialTimeout": 30,
"optimize": false
}
Response (200):
{ "iden": "<call iden>" }
destination: destination number (^[+*0-9]+$).iden(optional): caller-supplied id (^[A-Za-z0-9_-]{1,32}$); if omitted, the service generates one (16-char base62) and returns it in the response.maxDuration(ms, default10800000),dialTimeout(s, default30),optimize(bool, defaultfalse).
The returned iden identifies the generated call: it is used as the AMI
ChannelId and is propagated on leg 2 (the call towards the destination) to
the proxy as the SIP header X-Info-Click2Dial-iden. The proxy stores it as the
call IDEN for the leg, so it can be correlated with the originating /call
request from CDRs and realtime data.
Digest computation
The digest is computed on the client side, MD5-based, and only response
travels (the password never leaves the client):
HA1 = MD5(username : realm : password)
HA2 = MD5(destination)
response = MD5(HA1 : nonce : HA2)
The server recomputes and compares in constant time.
Note
In the browser crypto.subtle does not provide MD5; use a library such as
blueimp-md5 / crypto-js. CORS is solved by declaring the service origin in
the extension’s host_permissions; the service echoes allowed origins from its
configuration.
Error codes
Code |
Reason |
|---|---|
|
invalid JSON / AoR / destination / iden |
|
invalid, expired or reused nonce |
|
AoR not eligible (no terminal+endpoint+user+extension) or wrong digest |
|
could not resolve the Application Server (Kamailio) or originate (AMI) |
For security, the exact reason is not leaked between “the AoR does not exist” and
“wrong password”: both return 403.
Use case
A typical exchange from the browser extension:
POST /challengewith the logged-in terminal’saor→ obtainnonceandrealm.Compute
responsefromusername,realm,password,nonceand the clickeddestination.POST /callwithaor,nonce,responseanddestination→ the user’s terminal rings and, on answer, the destination is dialed. Keep the returnedidento correlate the call later.