Skip to navigation

Send and receive faxes

View as MarkdownOpen in Claude

SignalWire sends and receives faxes over phone calls. This guide shows two flows: send a PDF to a fax machine, and receive a fax on your SignalWire number.

How faxing works

A fax travels over a call. Once the call is answered, SignalWire and the fax machine on the other end negotiate a connection and transfer the document page by page. To send, SignalWire fetches a PDF from a URL you provide. To receive, SignalWire stores the incoming pages as a PDF and gives you its URL.

When a fax finishes, your app can read whether it succeeded, how many pages were transferred, and a description of the result. For received faxes, it can also read the PDF’s URL. A call carries one fax at a time. The sections below explain how to handle these results in each approach.

Choose an approach

Follow the section for your app’s approach:

  • SWML: add send_fax or receive_fax to your call document and branch on the result. Optionally send events to a webhook.
  • WebSocket (Relay): start the fax on an answered call and handle events in your code. No public webhook URL is needed.
  • REST: stop a fax by call ID from any process.

Prepare for faxing

Start with working call setup from Make and receive calls. To test, use a fax machine or online fax service that can send and receive faxes, and a publicly accessible PDF.

Trial projects only call and receive calls from verified numbers

A trial project only places calls to and receives calls from numbers you’ve purchased or verified. This also applies to fax calls. Prepare for your first call shows how to verify the fax number you’ll test with.

Replace these values in the samples you run:

ValueReplace with
<YOUR_SPACE>Your Space’s subdomain in <YOUR_SPACE>.signalwire.com
<YOUR_PROJECT_ID>Your Project ID
<YOUR_API_TOKEN>Your API token, used only in server code
<YOUR_CALLER_ID>Your SignalWire phone number, or a verified caller ID, in E.164 format
<YOUR_DESTINATION>The fax number you’ll send to, in E.164 format
<YOUR_FAX_DOCUMENT_URL>The public URL of a PDF that SignalWire can fetch and fax

Send and receive faxes via SWML

send_fax and receive_fax answer the call if it isn’t answered yet and pause the document until the fax finishes. Each then sets variables prefixed with send_fax_ or receive_fax_. Use switch to choose the next action.

VariableMeaningTypical action
send_fax_result, receive_fax_resultsuccess or failedBranch on the outcome
send_fax_document, receive_fax_documentThe URL of the PDF you sent, or of the PDF receivedSave or forward a received fax
send_fax_pages, receive_fax_pagesThe number of pages transferredCheck that every page arrived
send_fax_result_text, receive_fax_result_textA description of the result, such as why the fax failedLog the failure and retry or notify someone
send_fax_result_code, receive_fax_result_codeThe numeric fax result codeLog it with the description

send_fax_identity and receive_fax_identity hold the station ID SignalWire reported. send_fax_remote_identity and receive_fax_remote_identity hold the receiving or sending machine’s station ID.

Send a fax via SWML

Send a PDF to a fax machine with send_fax. header_info sets the text in the header line at the top of each page.

The SDK programs place the call as in Place a call via SWML, with the document built by the SDK’s SWML builder. The YAML and JSON tabs contain the equivalent complete document.

# Install: python -m pip install signalwire-sdk==3.4.1
# Save as send_fax.py and run: python send_fax.py
from signalwire import SWMLBuilder, SWMLService
from signalwire.rest import RestClient
client = RestClient(
project="<YOUR_PROJECT_ID>",
token="<YOUR_API_TOKEN>",
host="<YOUR_SPACE>.signalwire.com",
)
swml = (
SWMLBuilder(SWMLService(name="send-fax"))
.send_fax(document="<YOUR_FAX_DOCUMENT_URL>", header_info="Bayview Taxi receipt")
.build()
)
call = client.calling.dial(
from_="<YOUR_CALLER_ID>",
to="<YOUR_DESTINATION>",
swml=swml,
)
print(call["id"])

Run the example and check the fax machine for the document, with “Bayview Taxi receipt” in the header line.

If a person or voicemail might answer instead of a fax machine, check first with machine detection and run send_fax only in the fax branch.

Receive a fax via SWML

Receive a fax on your SignalWire number with receive_fax. The SDK programs build the document and save it as a hosted SWML Script, then print the script’s ID. The YAML and JSON tabs contain the same document, ready to paste into a hosted script in the Dashboard.

# Install: python -m pip install signalwire-sdk==3.4.1
# Save as receive_fax_script.py and run: python receive_fax_script.py
from signalwire import SWMLBuilder, SWMLService
from signalwire.rest import RestClient
client = RestClient(
project="<YOUR_PROJECT_ID>",
token="<YOUR_API_TOKEN>",
host="<YOUR_SPACE>.signalwire.com",
)
swml = (
SWMLBuilder(SWMLService(name="receive-faxes"))
.receive_fax()
.build()
)
script = client.fabric.swml_scripts.create(
name="Receive faxes",
contents=swml,
)
print(script["id"])

Assign the script to your phone number as in Answer a call via SWML, then send a test fax to the number. When it finishes, open Logs, then Calling, select the fax call, and open the URL in its Document field to view the PDF.

Track fax results via SWML

Add status_url to send_fax or receive_fax to receive JSON webhooks. These report the fax’s progress; use the variables above to branch in your document after the fax finishes.

Webhook fieldValue
event_typecalling.call.fax
params.call_idThe call ID
params.control_idThe fax operation’s ID
params.fax.typepage or finished
params.fax.params.directionsend or receive
params.fax.params.numberOn page, the number of pages transferred so far
params.fax.params.successOn finished, true or false
params.fax.params.pagesOn finished, the number of pages transferred
params.fax.params.documentOn finished, the URL of the PDF sent or received
params.fax.params.result_textOn finished, a description of the result
OutcomeCallbacks, in order
The fax succeedspage for each page, then finished with success: true
The fax fails partwaypage for each page transferred, then finished with success: false
The fax fails before the first pagefinished with success: false

A received fax’s PDF is in document on the finished callback when success is true. The receive_fax reference shows the full payload, and the webhooks guide covers endpoint setup and testing.

Set SWML fax options

Set these properties on send_fax. receive_fax takes only status_url.

SettingDefaultWhen to change it
documentRequiredThe public URL of the PDF to send
header_infoThe destination numberPrint your business name or a reference in the header line on each page
identityThe number you call fromReport a different station ID to the receiving machine
status_urlNoneReceive fax events at your webhook

The send_fax reference lists every property and the variables it sets.

Send and receive faxes via WebSocket (Relay)

Start a fax on an answered call, after registering a calling.call.fax handler. Read event.fax.type for the kind of event and event.fax.params for its details:

event.fax.typeMeaningTypical action
pageA page was transferred; params.number holds the pages so farLog progress
finishedThe fax ended; params.success says whether it succeededRead pages and document, or result_text on failure, then hang up

A failed fax still ends with finished, with success set to false. Handle completion in the calling.call.fax event handler, then hang up as shown below. Call the fax action’s stop() to stop a fax early.

Send a fax via WebSocket (Relay)

Dial the fax number, then send the PDF once the call is answered. The handler prints the result from the finished event and hangs up. The header and station ID settings work as in Set SWML fax options.

Call setup follows Place a call via WebSocket (Relay).

# Install: python -m pip install signalwire-sdk==3.4.1
# Save as send_fax_relay.py and run: python send_fax_relay.py
import asyncio
from signalwire.relay import RelayClient
from signalwire.relay.event import FaxEvent
client = RelayClient(
project="<YOUR_PROJECT_ID>",
token="<YOUR_API_TOKEN>",
contexts=["default"],
)
async def main():
async with client:
call = await client.dial(
devices=[[{
"type": "phone",
"params": {
"from_number": "<YOUR_CALLER_ID>",
"to_number": "<YOUR_DESTINATION>",
"timeout": 30,
},
}]],
)
async def on_fax(event: FaxEvent):
if event.fax.get("type") != "finished":
return
result = event.fax.get("params", {})
if result.get("success"):
print(f"Fax sent: {result.get('pages', 0)} pages")
else:
print(f"Fax failed: {result.get('result_text')}")
if call.state != "ended":
await call.hangup()
call.on("calling.call.fax", on_fax)
await call.send_fax(
document="<YOUR_FAX_DOCUMENT_URL>",
header_info="Bayview Taxi receipt",
)
await call.wait_for_ended()
asyncio.run(main())

Test with a fax destination and check the fax machine for the document.

Receive a fax via WebSocket (Relay)

Answer each call to your number, receive the fax, and print the stored PDF’s URL from the finished event. The handler subscribes to the inbound-fax topic.

# Install: python -m pip install signalwire-sdk==3.4.1
# Save as receive_fax_relay.py and run: python receive_fax_relay.py
from signalwire.relay import RelayClient
from signalwire.relay.event import FaxEvent
client = RelayClient(
project="<YOUR_PROJECT_ID>",
token="<YOUR_API_TOKEN>",
contexts=["inbound-fax"],
)
@client.on_call
async def handle_call(call):
async def on_fax(event: FaxEvent):
if event.fax.get("type") != "finished":
return
result = event.fax.get("params", {})
if result.get("success"):
print(f"Received {result.get('pages', 0)} pages: {result.get('document')}")
else:
print(f"Fax failed: {result.get('result_text')}")
if call.state != "ended":
await call.hangup()
call.on("calling.call.fax", on_fax)
await call.answer()
await call.receive_fax()
await call.wait_for_ended()
client.run()

Assign a Relay Application with the inbound-fax topic to your number as in Answer a call via WebSocket (Relay), then send a test fax and open the printed URL.

Stop a fax in progress via REST

If another process needs to stop a fax, send calling.send_fax.stop or calling.receive_fax.stop with the call’s ID and the fax’s control_id. Both IDs arrive in the fax’s events. The REST Calling API can’t start a fax; to send one over HTTP, use the dial request from Send a fax via SWML.

POST
/api/calling/calls
curl -X POST https://{your_space_name}.signalwire.com/api/calling/calls \
-H "Content-Type: application/json" \
-u "<project_id>:<api_token>" \
-d '{
"command": "calling.send_fax.stop",
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"params": {
"control_id": "fax-send-control-1"
}
}'

The command returns immediately. Only one fax can run on a call at a time, so the same control_id identifies it until it finishes.