Outbound Calls
Outbound lets the agent place a Teams call (a call-back), speak a result or hold a conversation, and hang up. It is optional and off unless configured.
Enable it
Section titled “Enable it”"outbound": { "enabled": true, "workerBaseUrl": "https://<your-standin-endpoint>", "tenantId": "<aad-tenant-id>", "answerTimeoutMs": 120000, "defaultMode": "notify"}| Key | Meaning |
|---|---|
outbound.enabled | Turn outbound on. |
outbound.workerBaseUrl | The StandIn outbound API base URL. |
outbound.tenantId | Your AAD tenant id for the callee. |
outbound.answerTimeoutMs | How long to wait for an answer before treating it as no-answer (default 120000). |
outbound.defaultMode | notify (speak the message and hang up) or conversation (stay and talk). |
How a call is placed
Section titled “How a call is placed”The agent triggers an outbound call through its realtime tools. Under the hood the plugin makes an HMAC-signed request to the StandIn outbound API:
- Signature headers
x-standin-timestamp/x-standin-signature, signed over"{timestamp}.{userObjectId}"with yoursecret. - The request identifies the callee (
userObjectId) andtenantId; StandIn returns acallId. - Requests are SSRF-guarded.
Once the callee answers, the same per-call WebSocket session begins and the conversation runs exactly like an inbound call.
No answer and cancel
Section titled “No answer and cancel”- The StandIn worker reports the real terminal state as soon as it knows it, so a declined or busy
call finalizes immediately instead of waiting out
answerTimeoutMs. See the outcome callback below. - If no outcome arrives, the attempt is finalized as no-answer when
answerTimeoutMsexpires. That timer is the fallback, not the primary path. - The plugin also best-effort cancels the ringing call so a late pickup does not strand the callee in a dead call.
- A late answer (after the timeout) is declined cleanly.
The outcome callback
Section titled “The outcome callback”StandIn POSTs the terminal state of a call you placed to the calling lane:
POST {calling path}/outcome/{callId}X-StandIn-Timestamp: <ms epoch>X-StandIn-Signature: HMAC-SHA256(secret, "{ts}.{callId}")
{"outcome": "declined"}- Path: under the calling prefix (default
/msteams/calling/outcome/{callId}), because a tunnel usually forwards only that prefix. No extra port or route to open. - Auth: the same HMAC contract as the WebSocket upgrade, over the callId - so a signature captured for one call cannot be replayed against another, and the timestamp must be inside the replay window.
- Body:
outcomeis one ofanswered,no-answer,declined,busy,failed.answeredis a no-op: the media socket attaches and the call proceeds normally. - Responses:
200 {"ok": true};401on a bad signature or a stale timestamp;400on a malformed body;404on any other path.
Nothing to configure - the route is served whenever the calling lane is. Before this existed the plugin learned only “no answer”, and only after its own timeout, so a declined call and an unanswered one were indistinguishable.
notify- the agent delivers a message and hangs up. Good for reminders and alerts.conversation- the agent stays on the line for a back-and-forth.
- Outbound needs the same
secretas inbound - it signs the place-call request. - Set
tenantIdto the callee’s tenant. - Keep
answerTimeoutMsrealistic (people take a few rings); too short gives up before they pick up.