Skip to content

QVOICE Platform Pivot — Custom application integration

HTTP contract for apps integrating with QVOICE Platform Pivot (POST JSON in/out).

Integrating a custom app with QVOICE Platform Pivot

This document is for application developers building the HTTP service that QVOICE Platform calls when a call reaches a Pivot step. It describes only what your app must implement: how requests look and what responses to return.


What your app must do

  1. Expose an HTTP endpoint that QVOICE Platform can reach (typically HTTPS).
  2. Accept POST requests whose body is a JSON object (Content-Type: application/json).
  3. Parse the incoming fields (see below). Treat the body as a flat JSON object; not every key appears on every request.
  4. Respond with 200 (or another 2xx) and a JSON body that describes the next callflow step QVOICE Platform should run (see Response below).
  5. Use Content-Type: application/json on the response.

If your integration also includes a separate URL for call-detail / hangup delivery, that callback (if used) is a different contract: QVOICE Platform may POST form-encoded data (not JSON) when the call ends. The voice webhook described here is always JSON in and JSON out.


Request (QVOICE Platform → your app)

Method POST
Content-Type application/json
Body UTF-8 JSON object

Fields you may receive

Keys are omitted when there is no value.

Common call context

Key Meaning
Call-ID Call identifier.
Account-ID Account ID.
From From user part.
From-Realm From realm.
To To user part.
To-Realm To realm.
Request Dialed / request user (DID context).
Request-Realm Request realm.
Call-Status Status when present on the session.
Api-Version e.g. "4.x".
Direction e.g. "inbound".
Caller-ID-Name Caller ID name.
Caller-ID-Number Caller ID number.
User-ID Owner user ID (one value or an array), if applicable.
Language Language when set.

Recording / transcription (when applicable)

Key Meaning
Recording-Url Recording URL when available.
Recording-Duration When set.
Recording-ID When set.
Transcription-ID When set.
Transcription-Text When set.
Transcription-Status When set.
Transcription-Url When set.

Other session fields (when applicable)

Key Meaning
Digits Digits or digit-related state when set.
RecordingUrl Alternate spelling may appear alongside Recording-Url depending on session state.
RecordingDuration Recording duration.
DialCallStatus After dial-related steps when set.
DialCallSid When set.
DialCallDuration When set.
QueueSid When set.
CallStatus Call status string when set.

Example request body

Illustrative only; many keys are absent on the first request.

{
  "Call-ID": "abc123@your.realm",
  "Account-ID": "account_id_here",
  "From": "1001",
  "From-Realm": "your.realm",
  "To": "2000",
  "To-Realm": "your.realm",
  "Request": "2000",
  "Request-Realm": "your.realm",
  "Api-Version": "4.x",
  "Direction": "inbound",
  "Caller-ID-Name": "Alice",
  "Caller-ID-Number": "+15551234567",
  "User-ID": "user_doc_id_32_chars_hex________",
  "Language": "en-us"
}

Identifying which app is invoked (APPID in voice_url)

The JSON body does not include an application id. QVOICE Platform calls whatever URL you configure in voice_url. Put your APPID (tenant, product, or integration key) in that URL so your server can route the request before it reads the JSON body.

Query parameter (typical)

https://your-app.example.com/voice?APPID=acme-crm

Path segment (typical)

https://your-app.example.com/v1/apps/acme-crm/voice

On each POST, your HTTP stack sees the full request target (path and query). Parse APPID from the query string or from the path, then use the JSON body for call fields (Call-ID, Account-ID, etc.).

Example nested pivot data using a query APPID:

{
  "module": "pivot",
  "data": {
    "voice_url": "https://your-app.example.com/voice?APPID=acme-crm",
    "method": "post",
    "req_format": "kazoo",
    "req_body_format": "json"
  },
  "children": {}
}

Use the same voice_url pattern everywhere you reference that integration (initial Pivot routing in QVOICE Platform and any pivot steps you return) so every callback is tagged with the same APPID.


Response (your app → QVOICE Platform)

Status 2xx with a body QVOICE Platform accepts (see below).
Content-Type application/json
Body One JSON object: a callflow action with module, data, and optional children.

4xx / 5xx: QVOICE Platform treats the Pivot step as failed; the call may continue elsewhere in the dial plan depending on how Pivot is wired.

On success, QVOICE Platform continues the call using the flow you return (bridge, voicemail, play, menu, etc.). Each module has its own required data shape; align data with the QVOICE Platform callflow modules your account uses.


Example responses

Use real document IDs from the QVOICE Platform account (voicemail box IDs are often 32 characters).

Bridge after setting caller ID

{
  "module": "set_cid",
  "data": {
    "caller_id_name": "Support Line",
    "caller_id_number": "+18005551212"
  },
  "children": {
    "_": {
      "module": "user",
      "data": {
        "id": "USER_DOC_ID",
        "timeout": 30,
        "strategy": "simultaneous"
      },
      "children": {}
    }
  }
}

Voicemail

{
  "module": "voicemail",
  "data": {
    "id": "0123456789abcdef0123456789abcdef",
    "action": "compose",
    "max_message_length": 500,
    "interdigit_timeout": 2000
  },
  "children": {}
}

Play audio

data.id can be a media ID or URL.

{
  "module": "play",
  "data": {
    "id": "http://example.com/audio/prompt.wav",
    "answer": true,
    "loop_count": 1,
    "endless_playback": false
  },
  "children": {}
}

Bridge to a PSTN number

Send the call out through your account’s carriers / trunks to a fixed public number using the resources module and to_did. Use E.164 (for example +15551234567) unless your deployment uses another numbering rule.

{
  "module": "resources",
  "data": {
    "to_did": "+15551234567",
    "timeout": 60,
    "caller_id_type": "external",
    "ignore_early_media": false,
    "use_local_resources": true
  },
  "children": {}
}

Outbound routing must be allowed for the account (carrier, rate deck, class of service). To chain caller ID first, wrap with set_cid and put resources under children._, same pattern as the user example.

DTMF via menu document

Pivot does not invoke your URL per keypress for this pattern. You return a menu module that points at a Menu document already defined in QVOICE Platform.

{
  "module": "menu",
  "data": {
    "id": "MENU_DOC_ID",
    "interdigit_timeout": 3000
  },
  "children": {}
}

Callback: another HTTP request after playback (or other steps)

Your first response can return a callflow that runs one or more modules and then invokes Pivot again. That sends another POST to your application (same URL or a different one) so you can make the next decision with an updated session (for example after a greeting or prompt has finished).

Mechanism:

  1. Include a pivot module in the tree, usually as the children._ step after another module (for example after play).
  2. In the pivot module’s data, set voice_url to the endpoint QVOICE Platform should call next, plus the same JSON conventions your integration uses (method, req_format, req_body_format as required for your app).
  3. When playback (or the previous step) completes, QVOICE Platform runs the pivot step and POSTs a new JSON body to that URL, same shape as in Request (QVOICE Platform → your app) above.
  4. Your handler for that URL returns the next JSON callflow action (bridge, voicemail, another play + pivot, etc.).

Example: play a recording, then call your app again

First response body (play greeting, then callback to your app):

{
  "module": "play",
  "data": {
    "id": "http://example.com/audio/welcome.wav",
    "answer": true
  },
  "children": {
    "_": {
      "module": "pivot",
      "data": {
        "voice_url": "https://your-app.example.com/voice?APPID=acme-crm",
        "method": "post",
        "req_format": "kazoo",
        "req_body_format": "json"
      },
      "children": {}
    }
  }
}

After the file plays, QVOICE Platform POSTs to that URL (including the APPID query if you used one) with the usual JSON body fields (same Call-ID for correlation). Your second response might bridge the call, send it to voicemail, or chain play and pivot again for another round trip.


Hospitality and PMS integrations

For hotels and PMS (property management system) workflows, plan for more than the voice Pivot JSON loop alone.

Voice Pivot + APPID
Use APPID in voice_url (see above) to route to the correct property, brand, or tenant before you parse the JSON body. Map dialed numbers or extensions from the request fields (for example Request, To, From) to room or guest context using your own rules and PMS data.

Hangup / call-detail callback
If you configure a separate URL for end-of-call delivery, QVOICE Platform can POST form-encoded (not JSON) data when the call finishes. That is the usual place to drive billing, call duration, reconciliation, or folio updates. Correlate with the same Call-ID you saw on the Pivot POSTs, plus APPID from the query string if you use the same URL pattern. Design handlers to be idempotent (retries or duplicate posts are possible).

Your app → PMS (outbound)
Business outcomes—wake-up completed, housekeeping ticket, minibar or telecom charge, guest verified—are typically your application calling the PMS (or a middleware API), not additional QVOICE Platform Pivot callbacks. Model those as separate REST or message flows from your hospitality service.

Recordings and voicemail
If your returned callflow uses recording or voicemail, URLs or identifiers may appear in session-related data when applicable. Use them only where policy and guest privacy rules allow (for example internal QA, not automatic PMS posting of audio).


Summary

Direction Method Content-Type Body
QVOICE Platform → app POST application/json Call and session fields (see above).
App → QVOICE Platform (reply) application/json One callflow action object (module, data, children).