Recipes← all recipesView on GitHub

Register a SIP endpoint and receive calls

VoiceSIP endpoint registration

Create SIP credentials for a softphone, register it as an endpoint, and route calls to its address.

Also called SIP registration, softphone or desk phone, Grandstream, Zoiper

sipcall-fabric

The claim

The vendored REST spec’s POST /api/fabric/resources/subscribers/{fabric_subscriber_id}/sip_endpoints “Create Subscriber SIP credential” requires exactly username and password. It also takes caller_id, send_as, ciphers, codecs and encryption, whose values are required, optional or default. The subscriber it hangs off is one POST /api/fabric/resources/subscribers with an email, and GET /api/fabric/resources/{id}/addresses lists the subscriber’s Fabric address. The bundled schema lists a “Call Fabric Resource address” among the forms connect.to takes. A document that connects to that address therefore sends a call to whatever registered with the credential. Whether it rings is the platform’s side of a live call. You reach the REST calls as client.fabric.subscribers.create, list_addresses and create_sip_endpoint.

How it works

def add_sip_credential(subscriber_id, username=SIP_USERNAME, password=SIP_PASSWORD,
                       caller_id=CALLER_ID):
    if not password:
        raise SystemExit("SIP_PASSWORD is required; see .env.example")
    return client.fabric.subscribers.create_sip_endpoint(
        subscriber_id, username=username, password=password, caller_id=caller_id)

def ring(address, service=None):
    service = service or SWMLService(name="ring", route="/ring")
    service.add_verb("answer", {})
    service.add_verb("connect", {"to": address, "timeout": 30})
    service.add_verb("hangup", {})
    return service

What the platform receives:

POST /api/fabric/resources/subscribers
{"email": "workshop@ridgeline.example", "display_name": "Workshop desk"}

GET /api/fabric/resources/<resource_id>/addresses

POST /api/fabric/resources/subscribers/<resource_id>/sip_endpoints
{"username": "workshop-desk", "password": "<from SIP_PASSWORD>", "caller_id": "+1555XXXXXXX"}

And the document a number runs, with the address from the listing:

{"version": "1.0.0", "sections": {"main": [
  {"answer": {}}, {"connect": {"to": "/private/workshop", "timeout": 30}}, {"hangup": {}}]}}

The function reads the password from SIP_PASSWORD when it runs, and a missing one stops the call before any request. The recipe leaves ciphers, codecs and encryption at the platform’s defaults; set them when your softphone needs a particular one.

Limitations

You prove the requests, the shapes and the document. Whether the softphone registers and rings, and which SIP domain it registers against, are the platform’s side. The domain is in your Dashboard, not in the vendored spec.

The spec also documents POST /api/fabric/resources/sip_endpoints, a credential that is not under a subscriber. Its documented required list includes fields a create cannot supply, such as id, so this recipe uses the subscriber form.

What to change first

Pass encryption="required" in add_sip_credential and run the verifier. The exact-body assertion fails, and the enum assertion says the value is legal. Put it in the expected body too, and the same document rings a softphone that only registers over TLS.