Call Flow

A call, end to end

This page traces a single inbound call from the moment it rings to the moment it ends — who talks to whom, and exactly which Confirm Reseller API call you make at each point. Everything you are responsible for is the Reseller lane; the Pindrop ↔ Confirm exchanges are shown so the timing makes sense, but they happen for you.

Who's involved

ParticipantRole
CallerThe member phoning the contact centre.
Phone system
SIPREC / SBC
Your (or the institution's) telephony. It forks a copy of the call audio to Pindrop over SIPREC and carries the unique SIP header that ties the leg to a Pindrop call.
PindropVoice biometrics, device / carrier risk and liveness analysis. Issues the call's voiceId and pushes events to Confirm.
ConfirmHolds the member record, the call record and the authentication policy. The only system you call.
Reseller youYour system — an agent desktop, a conversational AI or IVR platform, or middleware. Calls /external/* to claim the call, read results, and accept or reject the voice.
You never call Pindrop directly

Account correlation, authentication-result retrieval and authentication feedback are all made by Confirm on your behalf. Your integration is a handful of HTTP calls: queue, assign, details, and — only when it's needed — enroll or reject-voice.

Getting the voiceId

Every call-scoped endpoint is keyed on the call's voiceId, so the first question in any integration is how you get one. There are three ways, and most integrations use the first or the second.

1. Read it from the call queue

GET /external/calls/queue returns the institution's active calls — ones that haven't ended — each with its voiceId, the caller's phoneNumber, the start time, and the candidate members Confirm already matched from the core on that phone number. That last part is why this is the usual starting point: it gives you both the call handle and a shortlist of personId values to claim it with.

Response 200 (abridged)
{
  "success": true,
  "phoneCalls": [
    {
      "voiceId": "b1f0…9c2e",
      "phoneNumber": "+16165551212",
      "personId": null,
      "startDateTime": "2026-06-14T21:58:11+00:00",
      "persons": [ { "name": "Jane Doe", … } ]
    }
  ]
}

Supports searchString (matches the phone number or a matched member's name), startDate / endDate, and continueFrom / pageSize paging. Numbers on the institution's ignore list are filtered out.

2. Let Confirm resolve it from the phone number

If you already know the caller's number and which member is on the line, skip the lookup: POST /external/calls/assign with phoneNumber + personId, and the response hands you the voiceId to use from then on.

3. Receive a push notification from Confirm

Confirm can POST call events to an endpoint you own. An institution registers the URL under Settings → Push Notifications in the Confirm dashboard, optionally with custom request headers (entered as Header: value pairs separated by ; — use these to carry your own auth). Two event types are sent:

TypeFired when
CallStartPindrop reports the call has started — this is the earliest you can know the voiceId.
CallEndPindrop reports the call has ended.
POST to your endpoint — CallStart
{
  "Type": "CallStart",
  "StartDateTime": "2026-06-14T21:58:11.4821Z",
  "VoiceId": "b1f0…9c2e",
  "AlternateVoiceId": "k3Jd…9Aw",
  "PhoneNumber": "+16165551212",
  "DestinationPhoneNumber": "+18005550100",
  "IsTest": null,
  "InstitutionKey": "acme-cu"
}

CallEnd is the same shape with "Type": "CallEnd" and EndDateTime in place of StartDateTime. Note the PascalCase property names — these payloads are serialized differently from the API's own JSON responses.

Delivery is best-effort — don't depend on it alone

Confirm POSTs once and does not retry. Every attempt is recorded with the response status and body, visible on the same Push Notifications settings page, and that page can fire a sample CallStart / CallEnd at your endpoint while you build (samples carry "IsTest": true; real events don't). Treat the push as a low-latency nudge and keep the queue as your source of truth.

AlternateVoiceId works anywhere a voiceId does

Pindrop issues a second identifier for a call, delivered as AlternateVoiceId. assign, …/details and …/enroll all resolve an alternate id back to the real call, so you can pass whichever one you're holding.

The sequence diagram

sequenceDiagram
autonumber
actor Caller
participant PBX as Phone system
SIPREC / SBC participant PD as Pindrop participant CF as Confirm participant RS as Reseller Caller->>PBX: Call rings PBX-->>PD: Forked / forwarded audio Note over PD,CF: When the call is answered PD->>CF: Push notification — unique Call ID (voiceId) CF--)RS: Call-start push notification (if you registered an endpoint) rect rgb(230, 240, 251) Note over RS,CF: 1. Find the call and claim it RS->>CF: GET /external/calls/queue CF-->>RS: HTTP 200 — active calls, each with its voiceId RS->>CF: POST /external/calls/assign
voiceId or phoneNumber, + personId CF->>PD: Account correlation PD-->>CF: HTTP 200 CF-->>RS: HTTP 200 — { success, voiceId } end rect rgb(226, 245, 236) Note over RS,CF: 2. Read authentication results (poll) RS->>CF: GET /external/calls/{voiceId}/details CF->>PD: Fetch authentication results PD-->>CF: Authentication results CF-->>RS: HTTP 200 — policies, flags, Pindrop events end rect rgb(253, 244, 216) Note over RS,CF: 3. Decide — your system verified the caller RS->>CF: POST /external/calls/{voiceId}/enroll
personId + validationType CF->>PD: Account authentication feedback — SUCCESSFUL PD-->>CF: HTTP 200 CF-->>RS: HTTP 200 — { success } end Note over RS,CF: Or POST …/reject-voice → FAILED feedback, voiceprint un-enrolled rect rgb(238, 241, 244) Note over PD,CF: Call ended PD->>CF: Call-ended event — latest call analysis CF-->>PD: HTTP 200 CF--)RS: Call-end push notification (if you registered an endpoint) end
The blue, green and amber bands are the calls your integration makes. Everything else is handled for you.

Step by step, with API calls

  1. The call arrives and Pindrop starts analysing it

    The caller dials in; the SBC forks the audio to Pindrop over SIPREC. When the call is answered — by a live agent, an IVR, or a virtual agent — Pindrop pushes a notification to Confirm containing a unique Call ID, the value exposed to you as voiceId. Confirm creates the call record. No API call from you.

    Everything after this point is keyed on the voiceId, so your first job is to get hold of it — see Getting the voiceId below.

  2. Assign the caller to the call

    POST /external/calls/assign — bind the in-progress call to the member you have on the line (the identity claim). Send either voiceId or phoneNumber, plus the member's personId. Confirm resolves the call, records the claim, and correlates the account with Pindrop using the account alias {institutionKey}-{personId}.

    Request
    curl -X POST https://api.getconfirmed.org/external/calls/assign \
      -H "SsoToken: <sso-token>" \
      -H "InstitutionKey: acme-cu" \
      -H "RequestedBy: jdoe" \
      -H "Content-Type: application/json" \
      -d '{ "voiceId": "b1f0…9c2e", "personId": "00123" }'
    Response 200
    { "success": true, "voiceId": "b1f0…9c2e" }
    Send one identifier, not both

    If phoneNumber is present it wins and Confirm assigns by phone number; otherwise voiceId is used. With neither, the response is { "success": false, "error": "Please provide a phone number or voice id" }. The returned voiceId is the handle for every later call — keep it for the duration of the call.

  3. Read the latest authentication results

    GET /external/calls/{voiceId}/details — Confirm fetches the current authentication results from Pindrop and returns the full call record: the applied policies, the enrollment / verification flags, risk signals, and the Pindrop event log. This is the call you poll while the call is live.

    Request
    curl "https://api.getconfirmed.org/external/calls/b1f0…9c2e/details?checkVoiceAuthentication=true" \
      -H "SsoToken: <sso-token>" \
      -H "InstitutionKey: acme-cu" \
      -H "RequestedBy: jdoe"
    Response 200 (abridged)
    {
      "success": true,
      "voiceId": "b1f0…9c2e",
      "personId": "00123",
      "isVoiceEnrolled": true,
      "isVoiceVerified": true,
      "isVoiceMismatch": false,
      "isHighRiskDevice": false,
      "hasVoiceMatchValidationRule": true,
      "status": "FullAuthentication",
      "metaData": { "pindropPolicies": ["Green Device And Voice Match"] },
      "pindropEvents": [ … ]
    }
    Drive your caller-handling logic off metaData.pindropPolicies

    Turn the policies into a single status — a traffic light for a human agent, a branch for an automated flow — using the keyword rules documented under Interpreting the validation status. Never decide off the raw scores in pindropEvents, which are only present on Pindrop's call-end events.

  4. Enroll the voice once your system has verified the caller

    POST /external/calls/{voiceId}/enroll — call this when your system has established the caller's identity another way: knowledge-based questions, a one-time passcode, or a live agent's judgement. Confirm records the verification, sends Pindrop a SUCCESSFUL account-interaction feedback for {institutionKey}-{personId} against this voiceId, applies the Green Manual Authentication policy, and writes the AcceptCallerVoice and FullAuthentication activities.

    Request
    curl -X POST https://api.getconfirmed.org/external/calls/b1f0…9c2e/enroll \
      -H "SsoToken: <sso-token>" \
      -H "InstitutionKey: acme-cu" \
      -H "RequestedBy: jdoe" \
      -H "Content-Type: application/json" \
      -d '{ "personId": "00123", "validationType": "KnowledgeBasedAuth", "notes": "Verified by KBA" }'
    
    → { "success": true }

    This is what lets Pindrop keep the voiceprint, so the next call from this member can be voice-authenticated passively. Full detail in How voice enrollment works.

    Already voice-verified? You don't need to call /enroll.

    When …/details?checkVoiceAuthentication=true comes back with a green Pindrop policy, Confirm has already recorded the authentication as part of serving that request — it writes the FullAuthentication activity, marks the call voice-validated, and records a VoiceAuth verification against the member (pushed through to the core). Nothing further is required from you.

    /enroll exists for the case the passive check can't cover: a first-time caller with no voiceprint yet, or a call where the voice didn't match and your system verified the caller some other way. Calling it on an already-green call isn't rejected, but it records a second verification and re-sends the Pindrop feedback, so skip it.

    The other branch

    If the caller can't be trusted, POST /external/calls/{voiceId}/reject-voice instead. It sends Pindrop a FAILED interaction, records RejectCallerVoice, and un-enrolls every prior manual authentication for that member. See Rejecting & un-enrolling a voice.

  5. The call ends

    When the call hangs up, Pindrop posts its call-ended event to Confirm with the final call analysis — voice score, device risk, liveness — and Confirm settles the call record and its policies. No API call from you; a later …/details read reflects the final state.

Call summary table

The whole reseller surface used by this flow, in order.

#CallWhenYou get back
0 POST /external/token/request Once, then refresh on 401 (30-minute sliding expiry) token — the SsoToken for every call below
1 GET /external/calls/queue To find the live call and its voiceId — the usual starting point phoneCalls — active calls, each with voiceId, phoneNumber, and candidate members
2 POST /external/calls/assign As soon as you have an identity claim voiceId — the handle for this call
3 GET /external/calls/{voiceId}/details Repeatedly, while the call is live Policies, flags, risk, Pindrop events
4a POST /external/calls/{voiceId}/enroll The voice hasn't authenticated on its own and your system verified the caller another way. Not needed when …/details is already green. { "success": true } — voiceprint trusted
4b POST /external/calls/{voiceId}/reject-voice The caller could not be trusted { "success": true } — voiceprint discarded

Timing & retry notes

  • Assign early. Pindrop's analysis is keyed to the account alias, so the sooner the identity claim is correlated, the more of the call Pindrop can score against the right member.
  • Poll, don't wait. A Voice Pending status means Pindrop is still evaluating. Re-fetch …/details until it resolves or the call ends — results arrive mid-call, not all at once. Pass checkVoiceAuthentication=true: with false, Confirm returns the stored record and never asks Pindrop, so the status can never move.
  • Polling stops on its own. Confirm stops querying Pindrop for a call once it is fully authenticated, the voice has been rejected, a red policy is applied, the call has ended, or after 20 authentication checks. Past that point …/details keeps serving the stored result, so a loop left running won't burn Pindrop requests — but it also won't change.
  • Enroll only after the caller was actually verified. The enroll call tells Pindrop to trust this voiceprint on future calls; issuing it for an unverified caller poisons later authentications for that member.
  • Branch on success, not the HTTP status. Every response carries { "success": bool, "error": "…" }. A 401 means the SSO token lapsed — re-run the service-account login.

Open the API reference → Read the concept guides