architectureoperationssecurity

The screenshot is intentionally boring. It shows a sysadmin-only System Configuration tab with four fields: provider, account SID, auth token, and callback URL. That is exactly the right product shape. SMS configuration should be visible to the people operating the deployment, not hidden in process environment variables that require an engineer and a redeploy.

The field that deserves explanation is the one that looks most like a normal URL.

Osprey Strike System Configuration SMS tab showing Twilio live provider, account SID, masked auth token, and callback URL set to https://webhooks-main.constructured.com/webhooks/sms
The SMS tab stores the deployment-global Twilio settings. The callback URL shown here is the public delivery-status endpoint Twilio posts to.

The callback value in the screenshot is:

https://webhooks-main.constructured.com/webhooks/sms

That value is correct for the main Strike environment. It is not derived by copying the browser address bar. It is not the API gateway hostname. It is the public Cloudflare Worker hostname plus the SMS delivery-status path.

Key Takeaway

For a Strike UI domain https://osprey-<env>.constructured.ai/, the SMS delivery callback is https://webhooks-<env>.constructured.com/webhooks/sms.

The SMS tab is global configuration

Issue 1049 moved SMS setup out of process startup configuration and into System Configuration. That change matters operationally: a sysadmin can switch the provider, set the Twilio account, replace the encrypted auth token, and update the delivery callback without waiting for a redeploy.

The shape is intentionally hybrid.

The SMS provider is global. It decides which sending backend Strike uses for the deployment: mock for local or test behavior, live Twilio for real delivery. The callback URL is also global because Twilio needs one public delivery-status endpoint per deployment environment.

Per-NOC identity stays elsewhere. NOC-scoped phone numbers, active Twilio instance selection, and tenant identity are part of the NOC configuration surface. That is why the earlier cairn NOC-Scoped Does Not Mean NOC-Owned is still the ownership model: Strike owns the telephony plumbing; the NOC has scoped identity inside it.

In the SMS tab, the fields mean:

Field What it controls Operational note
Provider Whether SMS uses mock behavior or live Twilio This is deployment-global, not per NOC.
Account SID The public AC... Twilio account identifier used by the SMS sender It is an identifier, not a secret, but it should match the token.
Auth Token The Twilio signing and API secret Store encrypted; never render the previous value back in plaintext.
Callback URL The public delivery-status URL sent to Twilio as StatusCallback Must match the Worker URL exactly.
Definition

Twilio’s SMS StatusCallback is the URL Strike asks Twilio to POST delivery state changes to after a message is accepted: sent, delivered, failed, undelivered, and related transitions.

The callback URL is not the UI domain

The browser in the screenshot is on the Strike UI:

https://osprey-main.constructured.ai/...

That tells you the environment slug is main. It does not tell you the callback host directly.

The callback host uses a different subdomain and a different top-level domain:

https://webhooks-main.constructured.com/webhooks/sms

The derivation has three steps:

  1. Start with the Strike UI domain, such as https://osprey-main.constructured.ai/.
  2. Take the environment slug between osprey- and .constructured.ai; here, main.
  3. Build https://webhooks-<env>.constructured.com/webhooks/sms.

That gives the common environments:

Strike UI domain Environment slug SMS callback URL
https://osprey-main.constructured.ai/ main https://webhooks-main.constructured.com/webhooks/sms
https://osprey-demo.constructured.ai/ demo https://webhooks-demo.constructured.com/webhooks/sms

The slug convention is not enough by itself. The Worker host must also exist in the Cloudflare Worker configuration. In the infrastructure source, infrastructure/opentofu/cf-worker/terraform.tfvars carries the authoritative webhook_hostnames list. If webhooks-<env>.constructured.com is not in that list and applied, there is no front door for Twilio to reach.

Warning

Do not point the SMS callback at osprey-<env>.constructured.ai, and do not point it at <env>-gw.constructured.com. Twilio calls the public Worker host. The Worker forwards to the protected gateway.

Why exactness matters

There are two signature checks in the live path.

First, the Cloudflare Worker receives the Twilio webhook request at webhooks-<env>.constructured.com. It validates X-Twilio-Signature using the Twilio auth token. Then it derives the environment from the hostname and forwards the request to the protected backend gateway:

webhooks-main.constructured.com -> main-gw.constructured.com
webhooks-demo.constructured.com -> demo-gw.constructured.com

Second, the Strike API validates the Twilio signature again inside the /webhooks/sms handler. The important detail is that the API validator does not reconstruct the public URL from the backend request host. It validates against the configured callback URL string.

That is deliberate. Reverse proxies, TLS termination, and gateway forwarding can change the host the backend sees. Twilio signed the public URL it called, not necessarily the internal host the API received after forwarding. So the API holds the configured public callback URL and validates against that.

The consequence is simple: the stored callback string has to be exact.

If the SMS tab stores https://webhooks-main.constructured.com/webhooks/sms, and Twilio was given that same URL as the outbound message StatusCallback, the signatures line up. If someone stores https://main-gw.constructured.com/webhooks/sms, the Worker may still accept and forward a request, but the app’s second validation can reject it because the configured URL no longer matches the URL Twilio signed.

Key Takeaway

The callback URL is both outbound configuration and inbound validation material. It is not just where Twilio sends callbacks; it is also part of the signature contract Strike uses to decide whether callbacks are authentic.

What happens during delivery tracking

When Strike sends a live SMS, the Twilio sender creates a message through Twilio’s Messages API. If a callback URL is configured, Strike sets it as the message StatusCallback. If it is empty, the send can still happen, but delivery status will not flow back into Strike.

After Twilio accepts the message, delivery updates arrive asynchronously. Twilio posts form-encoded status data to /webhooks/sms, including the message SID and message status. Strike parses those values and records the transition against the delivery store.

That gives the ECO workflow a more useful record than “we tried to send a text.” The system can tell whether Twilio accepted the message, whether delivery progressed, and whether Twilio reported failure or non-delivery later.

The callback path is delivery status only:

/webhooks/sms

Inbound replies are a different product path. STOP, HELP, opt-in, and other inbound reply behavior belong to the separate inbound SMS webhook work, not to this delivery-status callback. Do not use /webhooks/sms/inbound in the delivery callback field.

How to determine the right value

Use this checklist when configuring a deployment:

  1. Open the Strike environment and read the UI hostname.
  2. Extract the environment slug from osprey-<env>.constructured.ai.
  3. Build https://webhooks-<env>.constructured.com/webhooks/sms.
  4. Confirm webhooks-<env>.constructured.com appears in the Worker webhook_hostnames list.
  5. Enter that exact URL in System Configuration -> SMS -> Callback URL.
  6. Save, then send or simulate a Twilio-signed delivery-status callback.

There are three common mistakes:

Mistake Symptom Fix
Using the UI domain Twilio reaches the wrong surface, or nothing useful handles the POST Use webhooks-<env>.constructured.com.
Using <env>-gw.constructured.com Request hits a protected backend path instead of the Worker front door Use the Worker host; let it forward to gateway.
Using the inbound reply path Delivery callbacks do not hit the delivery-status handler Use /webhooks/sms, not /webhooks/sms/inbound.

For a live verification, send a properly Twilio-signed POST to the derived URL. If the message SID is fake, the app may return a retryable store miss, but the HTTP status still tells you which layer accepted the request:

Response Meaning
503 The request passed both signature gates and reached the API; a fake or not-yet-recorded message SID missed the delivery row.
401 The Worker/front-door layer rejected the request or the route is not live.
403 The Worker accepted the request, but the API rejected the signature against the stored callback string or token.

That 403 is the useful clue. It usually means the URL in the SMS tab is not byte-identical to the public URL Twilio signed, or the auth token used by the app does not match the one Twilio used to sign the request.

The operator model

This configuration is a good example of why “global setting” does not have to mean “hard-coded deployment secret.” The value is deployment-wide because the public callback front door is deployment-wide. But the right operator should still be able to change it in the application, with a clear label and enough documentation to know what string belongs there.

That is the useful split:

  • Infrastructure defines which Worker hostnames exist.
  • System Configuration stores which callback URL this deployment tells Twilio to use.
  • The sender attaches that callback URL to outbound Twilio messages.
  • The Worker validates and forwards the callback.
  • The API validates the same URL again and records delivery status.

Each piece has one job. The SMS tab is the place where those jobs meet.

  1. The SMS callback is global. It belongs in System Configuration because it is deployment-wide Twilio plumbing, not per-NOC policy.
  2. The URL comes from the environment slug. osprey-main.constructured.ai maps to https://webhooks-main.constructured.com/webhooks/sms.
  3. The Worker is the public front door. Twilio calls webhooks-<env>; the Worker forwards to <env>-gw after validation.
  4. The exact string matters. Strike validates incoming callbacks against the stored callback URL, so near-misses can become signature failures.
  5. Delivery status is separate from replies. /webhooks/sms tracks Twilio delivery transitions; inbound STOP/HELP behavior uses a different endpoint.
  • Should the SMS tab derive and display a suggested callback URL from the current environment to reduce manual entry?
  • What UI copy would make the Worker-versus-gateway distinction clear without turning the form into infrastructure documentation?
  • Should the save flow perform a reachability or signed-callback verification before accepting a callback URL?
  1. Osprey Strike issue 1049 - The feature request and implementation decision thread for moving SMS configuration into System Configuration.
  2. Osprey Strike PR 1082 - The infrastructure documentation PR that records the `webhooks-.constructured.com/webhooks/sms` derivation rule.
  3. Twilio: Track outbound message status - Twilio's guide to delivery status callbacks for outbound messages.
  4. Twilio: Validating requests - Background on Twilio request signatures and why the exact public URL matters.
  5. NOC-Scoped Does Not Mean NOC-Owned - The related ownership model for Strike-owned telephony plumbing and NOC-scoped context.