Skip to navigation

Receive WhatsApp calls

View as MarkdownOpen in Claude

Once your WhatsApp number is connected and calling is turned on, a call to it is answered the same way as a call to a phone number. Follow Make and receive calls to build your handler, either via SWML or via WebSocket (Relay), with the differences on this page:

  • Every handler that runs on a Resource can answer.
  • You connect the WhatsApp number to the handler from the Resource, not from Phone Numbers.
  • You test from the WhatsApp app, and check the number’s voice fields if nothing answers.
  • Your business number arrives with a whatsapp: prefix.

Before you start

You need a WhatsApp business number that shows Registered in your Space, from Connect a WhatsApp business number, with calling turned on from Enable voice on a WhatsApp number. To test, you also need a phone with WhatsApp installed that can call the business number.

To connect the number in code, you also need your Space URL, the number’s SignalWire id from List WhatsApp numbers, and a Project ID and API token from the Dashboard’s API credentials page with the Voice permission, plus Numbers to list your WhatsApp numbers.

WhatsApp calls are audio only, can’t be placed on hold, and only arrive inbound. The overview’s What WhatsApp calling supports lists what works and what doesn’t before you build.

Choose a handler

Any handler from Make and receive calls that runs on a Resource can answer: a SWML Script, served from your server or hosted in SignalWire, or a Relay Application. An AI Agent, a Call Flow, or a Video Room can answer too, with a Video Room caller joining by audio only.

The Browser SDK can’t answer a WhatsApp call today, because a WhatsApp number can’t be attached to a Subscriber. The REST Calling API can’t answer one either, and can’t place calls to WhatsApp users.

Build the handler in Make and receive calls up to its assign step, then come back here. That guide’s assign steps, including the Assign the number in code samples, connect a phone number and don’t apply to a WhatsApp number, with two catches:

  • Relay Application. The Assign the topic to your phone number step also creates the Relay Application. Follow its Dashboard steps to create it, then skip the phone number assignment. To create it in code instead, see Connect the WhatsApp number via the REST API.
  • SWML Script served from your server. The From your server tab says you can skip its Dashboard steps because Assign the number in code creates the script. That doesn’t hold for a WhatsApp number, so create the script with the tab’s Dashboard steps.

Connect the WhatsApp number to the handler

This replaces the guide’s Assign the script to your phone number or Assign the topic to your phone number step.

Assignment doesn't check that voice is enabled

Neither the Dashboard picker nor the API checks that calling is turned on. A number you assign before enabling voice gets a handler but still can’t take calls.

Connect the WhatsApp number via the Dashboard

In your SignalWire Dashboard, open the Resource you want to answer the call and select its Addresses & Phone Numbers tab. Select + Add, then Assign a WhatsApp Phone Number, and select Add beside the number.

The picker lists Registered WhatsApp numbers that don’t already have an Address in your Space, whether that Address is for calling or for messaging. To point a number at a different Resource, delete its existing Address first.

Connect the WhatsApp number via the REST API

The Server SDKs don’t cover this endpoint yet, so call it directly. Put the Resource’s id in the path. The Create the hosted script in code samples print it; for any other Resource, find it with List Resources.

To create a Relay Application in code, send a topic that matches the contexts value in your Relay client. The response’s id goes in the path. Create Relay application lists every field:

POST
/api/fabric/resources/relay_applications
curl -X POST https://{your_space_name}.signalwire.com/api/fabric/resources/relay_applications \
-H "Content-Type: application/json" \
-u "<project_id>:<api_token>" \
-d '{
"name": "Booking Assistant",
"topic": "booking"
}'

In the body of the assign request, pass the WhatsApp number’s id as whatsapp_number_id with calling as the handler:

POST
/api/fabric/resources/:id/whatsapp_numbers
curl -X POST https://{your_space_name}.signalwire.com/api/fabric/resources/id/whatsapp_numbers \
-H "Content-Type: application/json" \
-u "<project_id>:<api_token>" \
-d '{
"whatsapp_number_id": "691af061-cd86-4893-a605-173f47afc4c2",
"handler": "calling"
}'

The response is the Address SignalWire created for the number on that Resource:

Response
{
"canonical_uri": "/public/justice-league",
"channels": {
"audio": "/external/resource_name?channel=audio"
},
"cover_url": "https://coverurl.com",
"display_name": "Justice League",
"id": "691af061-cd86-4893-a605-173f47afc4c2",
"locked": true,
"name": "justice-league",
"preview_url": "https://previewurl.com",
"resource_id": "993ed018-9e79-4e50-b97b-984bd5534095",
"type": "app"
}

Unlike the Dashboard picker, the API accepts a number that already has a calling Address and re-points it at the new Resource. A WhatsApp number holds one Address at a time, though, so assigning a calling handler removes any messaging Address the number had. The Assign Resource to WhatsApp number reference lists every field and error.

Call the number from WhatsApp

Open a chat with your business in WhatsApp and tap the call button. You hear your handler’s greeting, such as “Hello, welcome to SignalWire!”, then the call ends.

If nothing answers, list your WhatsApp numbers and read two fields on yours:

GET
/api/messaging/whatsapp/numbers
curl https://{your_space_name}.signalwire.com/api/messaging/whatsapp/numbers \
-u "<project_id>:<api_token>"

voice_capable is true only when calling is enabled and a handler is attached. If it is false and voice_enabled is also false, calling was never turned on or Meta refused a renewal; Enable voice on a WhatsApp number covers both. If voice_enabled is true, check calling_handler_resource_id in the same response. Null means the handler never attached, or its Address was deleted since: connect the number again and check that response for errors.

If both fields are true and the call still fails, the handler itself is the problem. Use the troubleshooting in the matching section of Make and receive calls, but skip its checks of the number’s Inbound Call Settings, which a WhatsApp number doesn’t have. For a Relay Application, check instead that the WhatsApp number is attached to it and that your client’s contexts matches the Resource’s topic.

Read the caller and business numbers

When SignalWire runs your Resource, it hands over the call with your business number as to and the caller’s number as from. Your business number carries a whatsapp: prefix. The caller’s number is plain E.164, the phone number their WhatsApp account is registered to, so you can look a customer up the same way you would on an inbound phone call.

For an AI Agent or SWML Script served by the Server SDKs, both numbers arrive in the JSON body SignalWire POSTs to your server before the call starts, nested under call. On a WhatsApp call, call.to is whatsapp:+15557654321 and call.from is +15551234567. The inbound call webhook reference documents every field of that body:

Read the caller’s number via SWML

Because the prefix only appears on to, one agent can serve both a phone number and a WhatsApp number and adjust to how the call came in. Register a dynamic configuration callback with set_dynamic_config_callback in Python or setDynamicConfigCallback in TypeScript. It runs once per call with the request body and an ephemeral copy of the agent, so the WhatsApp-specific section applies to that call only:

# Install: python -m pip install signalwire-sdk==3.4.1
# Save as whatsapp_agent.py and run: python whatsapp_agent.py
from signalwire import AgentBase
agent = AgentBase(name="ada", route="/ada")
agent.set_prompt_text("You are Ada, the dispatcher at Bayview Taxi. Help callers book a ride.")
def configure_for_channel(query_params, body_params, headers, agent_copy):
call = body_params.get("call", {})
if call.get("to", "").startswith("whatsapp:"):
agent_copy.prompt_add_section(
"Channel",
body=(
"The caller reached Bayview Taxi from WhatsApp. Thank them for calling on WhatsApp. "
"Hold isn't available on this call, so never offer to place them on hold."
),
)
agent.set_dynamic_config_callback(configure_for_channel)
agent.serve()

This agent runs on your own server, so SignalWire has to reach its URL. Create a SWML Script Resource that uses an External URL pointing at the agent, as the From your server tab of Create the SWML Script shows, then connect the WhatsApp number to that Resource.

Past that point the call is an ordinary call. Recording, transcription, and the rest of SWML work as they do on a phone call, with the hold exception noted above.

Find the calls in your logs

Find WhatsApp calls via the Dashboard

WhatsApp calls appear alongside the rest of your voice traffic in your SignalWire Dashboard under Logs → Voice. The To column shows the whatsapp:-prefixed business number, which is the quickest way to pick them out.

Find WhatsApp calls via the Voice Logs API

The same records come back from the Voice Logs API. A WhatsApp call is logged as a Relay PSTN call whose to is the whatsapp:-prefixed business number:

GET
/api/voice/logs
curl https://{your_space_name}.signalwire.com/api/voice/logs \
-u "<project_id>:<api_token>"
Response
{
"links": {
"self": "https://example.signalwire.com/api/voice/logs?page_number=0&page_size=50",
"first": "https://example.signalwire.com/api/voice/logs?page_size=50"
},
"data": [
{
"billing_ms": 60000,
"charge": 0.01,
"charge_details": [],
"created_at": "2024-05-06T12:20:00Z",
"direction": "inbound",
"duration": 42,
"duration_ms": 42310,
"from": "+15551234567",
"id": "7c1f6c1e-3b2a-4d9e-9f0b-2a6c8e5d4f31",
"parent_id": null,
"source": "realtime_api",
"status": "completed",
"to": "whatsapp:+15557654321",
"type": "relay_pstn_call",
"url": null
}
]
}

The endpoint filters by date rather than by number, so request the window you need and match to against your business number. See List voice logs for the date filters and every field.

Next steps