Skip to main content
Build a small application that joins an SMS request to a saved message ID and a persisted status observation. The receiver verifies the exact signed bytes and stores each event before acknowledging it. A separate worker retrieves the current SMS resource so a late notification does not overwrite status using arrival order. This tutorial uses Node.js 24 and SQLite on one machine. It uses ordinary HTTP clients, not an official SDK. The Send an SMS and Message status guides retain cURL, JavaScript, Python and Java request examples; this application tutorial focuses on Node.js.

1. Get the application and run the offline test

Use Node.js 24. No npm install is needed for this application. SQLite may print an experimental API warning. The test starts local HTTP servers and a temporary database; no credentials or live SMS sends are needed. Read or download the complete application and README. The test exercises a lost acceptance response, database restart, duplicate and out-of-order events, stale signatures, secret-version checks, rejected business and event IDs, failed storage and a worker retry. A local API stub proves the application’s recovery paths; it does not establish live delivery or charging.

2. Prepare approved Sandbox access

Complete the Quickstart. You need a Sandbox access token, business ID, authorized sender and permitted test recipient. Use the same business throughout this tutorial, with access to send SMS, retrieve message status and configure webhooks. If these features are unavailable for your business, contact Support before continuing. In each terminal used below, set the same environment variables:
Use a persistent local directory and a separate database per API host and business. The database stores recipient numbers and message content: restrict its directory and backups to authorized users. Keep secrets out of source control.

3. Register and start the receiver

Prepare an HTTPS URL routed to http://127.0.0.1:8080/hooks/1funnel by your trusted reverse proxy or development tunnel. Preserve the request bytes and webhook headers; expose only this path. Follow Configure endpoints and Create a webhook endpoint to register that HTTPS URL with these event types:
Check HTTP 201, the accepted subscriptions and any warnings before proceeding. Save the returned endpoint ID and securely retain the signing secret. Use the original secret version 1 until an operator coordinates a rotation. Create private keys.json:
The example end date is a configurable bound, not an API secret lifetime. For rotation, install both versions with operator-agreed activation and retirement windows, then restart the receiver. See Overlapping secret versions.
The receiver checks HMAC, timestamp tolerance, trusted secret version, business, event type and the signed envelope ID against the header. It stores the inbox row before returning 204; duplicates also receive 204. Storage failure returns 503 so delivery can retry. Verification failure returns 401.

4. Submit one SMS and save its ID

In a second terminal in the same directory, create request.json. Replace the sender and recipient with your approved values:
The application saves a key and the request bytes before making Submit an individual SMS. It requires HTTP 202, then saves the returned id and status. Acceptance is not delivery confirmation. The actual response supplies the ID; do not copy one from an example. If the connection fails, run the exact same command with the unchanged file and database. The application reuses the saved key. Once a message ID is saved, repeating the command returns that saved result without another POST. A changed payload under the same application operation ID is rejected. Review HTTP errors before retrying; do not change the key to bypass them. The idempotency replay window and recovery rules apply. If acceptance remains uncertain after seven days, investigate the original request instead of inventing a new key or deleting local state.

5. Process notifications and inspect the saved status

In a third terminal, with the same environment and database:
work processes up to 50 pending inbox entries and exits. Repeat it periodically; run only one worker at a time. For each known message ID it calls Retrieve an individual SMS, requires HTTP 200, then commits the snapshot and processed marker together. inspect shows message_id, status, checked_at and inbox processed_at. A status of delivered records the observed delivery outcome; sent alone does not confirm handset delivery. A failed GET leaves the event pending for another worker attempt. Respect rate limits and Retry-After when scheduling retries. An event that arrives before the acceptance response stays pending until the saved send has a message ID. The worker reads current REST state instead of treating a late sent event as newer than a delivered event. See the published delivered event for its actual envelope and fields.

6. Recover a missing notification

Run reconciliation when no worker is active. This retrieves the saved message ID and persists its current status without sending another SMS. If the ID was never saved, resolve the original send first. For delivery investigation, follow Webhook retries and recovery. A missing notification is not a reason to repeat the send.

Before using this design in production

The application deliberately has one worker, no external business side effects and a local SQLite database. Add supervised execution, retry backoff, monitoring for pending and unknown-message events, storage limits, retention and tested backups before operating it. Monitor checked_at: a saved snapshot is not a promise that status will never change. Keep the receiver clock synchronized. Scaling workers requires coordinated claims; adding business effects requires an idempotent transaction or outbox design. See the application README for the single-machine boundaries and durable receipt patterns.