Zum Hauptinhalt springen

Grok (xAI) Setup

Unlike the OpenAI integration, there is no dedicated Grok trunk type in Vodia PBX. The Grok SIP path is configured as a generic SIP gateway trunk pointed at xAI's SIP ingress, combined with a phone number registered against your xAI account via their API.

Tip: You will need an xAI account with API access and credits loaded before proceeding.

How this differs from OpenAI

OpenAIGrok (xAI)
SIP destinationsip:{project_id}@sip.api.openai.comsip:{number}@sip.voice.x.ai;transport=tls — a phone number you register is the SIP user
Trunk typeDedicated OpenAI trunk type in VodiaGeneric SIP gateway trunk
Accepting the callPOST /v1/realtime/calls/{call_id}/accept before opening the WebSocketNo accept step. Opening the WebSocket on the call_id is what answers the call
Number provisioningNot applicableRegistered via POST /v2/phone-numbers before any call can be made

Step 1 — Create an xAI API key

Log into the xAI Cloud Console and create an API key. You'll use it both for number registration below and inside the Voice Agents script.

export XAI_API_KEY="your-key-here"

Step 2 — Register a phone number with xAI

xAI does not provision numbers. You register a number under origin: "byo_trunk", and xAI matches inbound SIP against it. The number does not need to be dialable on the PSTN — it is only ever used between your PBX and xAI — but it must be a well-formed E.164 number, since xAI validates the format.

curl -X POST "https://api.x.ai/v2/phone-numbers" \
-H "Authorization: Bearer $XAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"origin": "byo_trunk",
"name": "Vodia PBX voice agent",
"phone_number": "+1XXXXXXXXXX",
"webhook": {
"name": "Vodia PBX webhook",
"url": "https://your-pbx-fqdn/grok"
},
"sip_auth": {
"allowed_addresses": ["YOUR.PBX.PUBLIC.IP/32"]
}
}'

Two fields matter beyond the obvious:

  • webhook.url path must match the dialplan pattern used in call.dial(). If the script calls call.dial('grok'), the webhook URL must end in /grok. This is the binding between an outbound call and the script that receives xAI's callback — a mismatch here is the most common cause of a call that rings forever and never answers.
  • sip_auth.allowed_addresses must be the address xAI actually sees, not necessarily the address your PBX listens on. On a NAT'd or reserved-IP host, confirm with:
    curl -s https://api.ipify.org; echo
    run from the PBX itself, and use that address rather than assuming.

The response includes a webhook signing secret, returned only once:

{
"phone_number": { "phone_number_id": "phone_...", "phone_number": "+1XXXXXXXXXX", "..." : "..." },
"webhook": { "webhook_id": "webhook_...", "dispatch_signing_secret": "whsec_..." }
}

Store it securely — it cannot be retrieved again afterward.

note

Vodia's Voice Agents JavaScript environment does not expose an HMAC primitive, so this signature cannot be verified inside a script. Treat the webhook URL itself as a shared secret, and restrict it at the firewall to xAI's outbound ranges if stronger assurance is required.

Step 3 — Create the Groktrunk

Create a new Grok trunk in Vodia PBX for connection to Grok

caution

Some trunk configurations normalize the outgoing number to national format (dropping the country code) by default. xAI matches on the exact registered E.164 string, including the leading +. Confirm the trunk's number-format setting is global/E.164, not national — a mismatch here produces a 407/401 challenge from xAI's SIP ingress that looks like an authentication failure but is actually a routing mismatch.

Step 4 — Create the dialplan entry

Pattern:     grok
Replacement: 1XXXXXXXXXX (the exact number from Step 2, no need to include + as the trunk will add it)
Trunk: the trunk created in Step 3

A script then reaches Grok with:

call.dial('grok')

which the dialplan rewrites into sip:1XXXXXXXXXX@sip.voice.x.ai;transport=tls.

Step 5

Under tenant general setting make sure the prefix grok is set for URL Prefix for Incoming AI Webhooks. You can have multiple prefixes with space as delimiter for eg openai grok

Step 6 — Verify

Place a test call through a script that only logs its call.http() argument and calls call.dial('grok'). In the SIP trace you should see:

  1. An INVITE to sip:+1XXXXXXXXXX@sip.voice.x.ai;transport=tls
  2. A 180 Ringing
  3. Within a second or two, a realtime.call.incoming webhook containing data.call_id

The call continues ringing until a script opens a WebSocket on that call_id — there is no separate accept step. See the Grok basic call transfer example for the full script.

Troubleshooting

SymptomLikely cause
407/401 on the INVITER-URI doesn't exactly match the registered number (national vs. E.164 format), or the source IP isn't in allowed_addresses
404 Not Found on the INVITEDialplan replacement is empty or malformed
Webhook never arrivesWebhook URL unreachable from the internet, or DNS/TLS misconfigured on the PBX
Webhook arrives, but the script never sees itThe webhook path doesn't match the call.dial() string used to place the call — the two must be the same word
Call rings indefinitelyExpected until a script opens the WebSocket on call_id — this replaces OpenAI's explicit accept step

Your Grok integration is now configured and ready to use by your Voice Agents JS script.