Skip to content

Custom SMS Gateway Implementation

This page explains how to connect an SMS carrier that Thirdlane does not support out of the box by using the Custom gateway provider. It covers the Configuration Manager fields, the webhooks the platform exposes, the request/response contracts your adapter must speak, and an optional reference adapter (a small local HTTP service that bridges Twilio to the Custom-provider contract) that you can download and run for lab testing.

If you just need to configure a standard Telnyx/Twilio/Bandwidth gateway, see Messaging Gateways.

What the Custom provider gives you

Selecting Provider = Custom on an SMS gateway tells Configuration Manager to use its generic SMS contract instead of a built-in carrier driver. Once saved, the platform exposes:

  • Outbound sending — Configuration Manager submits outbound messages to the URL you configure in Provider’s URL. Your adapter or your carrier’s REST endpoint is responsible for actually placing the message with the carrier.
  • Inbound webhookhttps://<domain>/sms/<gateway-name>/generic on the selected gateway domain. Your adapter (or the carrier itself, if it can be configured to post directly) forwards received SMS/MMS events here.
  • Delivery status webhookhttps://<domain>/sms/<gateway-name>/generic/status for delivery receipts.

Replace <domain> with the value picked in the gateway’s Domain field and <gateway-name> with the gateway’s Name.

Configuration Manager fields

Open Configuration Manager -> Communications Settings -> Messaging Gateways and create a new gateway.

  • Name — unique alphanumeric name (no spaces). Appears in the generated webhook URLs.
  • Provider — set to Custom.
  • Description — optional.
  • Domain — public domain that Twilio / your carrier / your adapter will reach inbound and status webhooks on.
  • Provider’s URL — full URL to which outbound send requests will be submitted. Point this at your own adapter, your carrier’s API, or (for lab testing) at the bundled reference sample gateway.
  • Account SID — identifier passed in outbound requests, reused as a generic credential.
  • Auth Token — secret paired with Account SID.

After the gateway is saved, the form shows the generated Webhook URL and Delivery Status URL. Configure those at your carrier’s portal (or, for the reference adapter, at Twilio).

You still need to flag the relevant DIDs as SMS-enabled and configure Inbound Messaging Routes so that inbound messages reach users or queues.

Message flow

flowchart LR
App["Your app / Configuration Manager"] -->|"outbound send"| Adapter["Your adapter or carrier API<br/>(Provider's URL)"]
Adapter -->|"carrier API call"| Carrier["SMS carrier"]
Carrier -->|"inbound SMS"| Adapter
Carrier -->|"delivery receipt"| Adapter
Adapter -->|"POST /sms/&lt;name&gt;/generic"| Platform["Thirdlane Platform"]
Adapter -->|"POST /sms/&lt;name&gt;/generic/status"| Platform

Your adapter is the translation layer: it speaks the carrier’s API on one side and the Custom-provider contract on the other. Some carriers can be configured to POST directly to the platform webhooks, in which case the adapter is only needed for outbound translation (or not at all, if the carrier’s outbound API is compatible with what Configuration Manager submits).

Request/response contracts

The platform uses a Twilio-compatible form-encoded shape for outbound, inbound, and status events.

1. Outbound send

Configuration Manager submits a POST to the configured Provider’s URL with:

  • Content-Type: application/x-www-form-urlencoded
  • Fields:
    • From — sender number in E.164
    • To — recipient number in E.164
    • Body — message text
    • MediaUrl — (optional) media URL for MMS
    • StatusCallback — the generated Delivery Status URL for the gateway

Your endpoint should respond with JSON compatible with Twilio’s Messages.json response (at minimum a message sid and an initial status such as queued).

2. Inbound webhook

Your adapter (or the carrier) posts inbound events to:

POST https://<domain>/sms/<gateway-name>/generic
Content-Type: application/x-www-form-urlencoded

Typical fields:

  • MessageSid
  • From
  • To
  • Body
  • NumMedia
  • Media fields when NumMedia > 0: MediaUrl0, MediaContentType0, …

3. Delivery status webhook

Delivery updates post to:

POST https://<domain>/sms/<gateway-name>/generic/status
Content-Type: application/x-www-form-urlencoded

Typical fields:

  • MessageSid
  • SmsSid
  • MessageStatus / SmsStatus (queued, sent, delivered, undelivered, failed, received)
  • From
  • To
  • RawDlrDoneDate (optional, carrier-specific)

Reference sample gateway (Twilio adapter)

A minimal reference adapter is provided as a starting point. It runs as a small local HTTP service that:

  • Sends outbound SMS through the Twilio API.
  • Receives inbound SMS/MMS from Twilio and forwards the payload to your upstream Custom-provider inbound URL.
  • Forwards delivery status callbacks from Twilio to your upstream status URL.

Three equivalent implementations are provided; pick whichever language your operators are comfortable with:

  • sms_gw.pl — Perl.
  • sms_gw.py — Python 3, standard library only (no pip packages).
  • sms_gw.js — Node.js, built-in modules only (no npm install).

Only one sample can listen on the default port (65533) at a time.

Configuration constants

Edit the constants at the top of the sample you choose:

  • SID — Twilio Account SID.
  • TOKEN — Twilio Auth Token.
  • DOMAIN_API_SMS_SERVICE — your upstream host (the Domain set on the Custom gateway).
  • URI_API_SMS_SERVICE_INBOUND — upstream path for inbound events, e.g. /sms/<gateway-name>/generic.
  • URI_API_SMS_SERVICE_STATUS — upstream path for delivery status events, e.g. /sms/<gateway-name>/generic/status.

Local listener defaults:

  • Address: 127.0.0.1
  • Port: 65533

Local routes served by the sample

These routes are sample-only and intentionally prefixed with /testsms/ so they do not collide with the platform’s production /sms/... webhooks:

  • GET/POST /testsms/generic/send — sends an SMS using form fields from, to, body, optional mediaUrl.
  • POST /testsms/generic — receives inbound Twilio payload and forwards it to your upstream inbound URL.
  • POST /testsms/generic/status — receives Twilio status callbacks and forwards them to your upstream status URL.

Example outbound call:

Terminal window
curl -X POST "http://127.0.0.1:65533/testsms/generic/send" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "from=+14159917673" \
--data-urlencode "to=+15555550123" \
--data-urlencode "body=Hello from Custom provider"

Run

From the directory where you saved the sample, run one of:

Terminal window
perl sms_gw.pl
python3 sms_gw.py
node sms_gw.js

Expected startup output:

Upstream: http://127.0.0.1:65533/

Exposing the sample via Nginx

If Twilio (or Configuration Manager) must reach the sample over HTTPS on your server, add the bundled Nginx snippets.

Upstream — save as /etc/nginx/conf.d/https/upstream/custom/custom_sms_gw.conf:

upstream custom_sms_gw {
server 127.0.0.1:65533;
}

Service location — save as /etc/nginx/conf.d/https/service/custom/custom_sms_gw_svc.conf:

location /testsms {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://custom_sms_gw;
proxy_read_timeout 86400s;
proxy_http_version 1.1;
}

Then reload Nginx:

Terminal window
sudo nginx -t && sudo systemctl reload nginx

Moving toward production

The reference adapter is a teaching tool. Before relying on a Custom SMS gateway in production, review each of these with your operations and security teams.

1. Define a canonical event model

Normalize messages into a stable internal shape independent of the carrier: message_id, provider_message_id, direction, from, to, text, media[], status, provider_raw_payload, received_at.

2. Separate concerns

Split the adapter into clear layers: transport (HTTP server/client), provider adapters (carrier-specific send and webhook mapping), business logic (validation, normalization, persistence, retries), and observability (structured logs, trace IDs, metrics).

3. Validate input strictly

Enforce E.164 phone format, message length policy, allowed media MIME types; reject malformed callbacks with HTTP 4xx.

4. Verify webhook authenticity

Validate carrier signatures (for Twilio, verify X-Twilio-Signature). Reject unsigned or invalid callbacks. Enforce HTTPS and, where available, timestamp replay protection.

5. Add reliability guarantees

Retry transient failures (429/5xx, timeouts) with exponential backoff. Use an idempotency key per message SID. Persist delivery attempts and outcomes. Deduplicate repeated callbacks.

6. Build a full status state machine

Normalize statuses across carriers (queued, sent, delivered, undelivered, failed, received) and reject invalid transitions (for example, delivered -> queued).

7. Design for multi-provider

Abstract provider-specific code behind send(provider, payload), parse_inbound(provider, raw_event), parse_status(provider, raw_event) to make switching carriers low-risk.

8. Harden security

Move secrets to environment variables or a secret manager. Enable TLS certificate verification. Authenticate internal send endpoints. Add rate limiting and request size limits. Sanitize logs to avoid leaking PII and secrets.

9. Add observability and alerting

Track outbound send success/failure, callback processing latency, carrier API latency and error rates, and the delivery funnel. Alert on sudden drops in deliveries, spikes in 401/403/429/5xx from the carrier, and callback endpoint failures.

10. Build a test matrix

At minimum: valid outbound SMS, MMS with media URL, invalid destination, carrier timeout/retry path, inbound SMS without media, inbound MMS with multiple media items, status transitions through all terminal states, and duplicate callback delivery.