Carrier-facing inbound SMS webhook (MS1 Task 2)

FeatureVOIPService
Ya
27 Agosti 2026, 18:34 UTC
Mwandishi
Kamo
Ahadi ya
1899050

POST **************** -- form-encoded or JSON, guarded by the CARRIER'S OWN SIGNATURE. Why a new endpoint. /api/voip/webhook cannot receive a STOP and fails silently: it readTree()s the body with no `consumes` restriction, every bulk-text carrier posts **************** the parse throws, the exception is swallowed and the carrier is answered 200 OK -- so it never retries. Confirmed in the current source before writing this. Why this path. **************** holds "/api/bulktext/send" and matches with startsWith, so anything under it demands an X-Internal-Auth header a carrier cannot send. /api/bulktext/inbound is a SIBLING of /send, is added to **************** and is deliberately NOT registered in InternalAuthFilter. Twilio signature: HMAC-SHA1 over the full URL (query string included) plus the POST parameters appended name-then-value in case-sensitive sorted order, base64, compared to X-Twilio-Signature with MessageDigest.isEqual. Verified against Twilio's security documentation, then pinned to the published vector from Twilio's own twilio-java RequestValidatorTest (token 12345 -> RSOYDt4T1cUTdK1PDd93/VVr8B8=), so the test cannot pass for a wrong algorithm. Modelled on MicrosoftTeamsProvider (blank secret -> log + false; exception -> false; constant-time compare); NOT on RingCentralProvider's `return true; // TODO`, and RingCentralSignatureUtil's equalsIgnoreCase is not reused. The signed URL is rebuilt from X-Forwarded-Proto/Host, not getRequestURL(). The edge terminates TLS, so getRequestURL() returns the container's own scheme and host, which is not what Twilio signed -- every signature would fail and the failure would look exactly like a forgery. FLOWROUTE IS PARSE-ONLY AND STILL REFUSES EVERY WEBHOOK. Flowroute publishes no webhook signature: its inbound-message docs specify the callback URL, the application/vnd.api+json content type and the payload shape, and no signature header, HMAC scheme or shared secret. There is nothing to verify, so it keeps the fail-closed default rather than getting an invented scheme -- an invented one would either reject all genuine traffic or be written to return true, which is the forgery surface this interface exists to prevent. Flowroute also does not post form-encoded, so `consumes` accepts vnd.api+json too. Sinch, Infobip and Vonage are unimplemented on both methods and log a warning naming themselves. TDD evidence ------------ Red (class absent): [ERROR] **************** cannot find symbol symbol: class BulkTextInboundController location: class **************** Green: Tests run: 27, Failures: 0, Errors: 0, Skipped: 0 Mutation 1 -- THE ONE THE PLAN NAMES, and it proves nothing. Plan Task 2 Step 8 says: make validateWebhookSignature default to `true` and aMissingWebhookSecretRejects must go red. Observed: Tests run: 24, Failures: 0, Errors: 0, Skipped: 0 [INFO] BUILD SUCCESS Nothing went red. Every test drives Twilio, which OVERRIDES the default, so the fail-closed default was exercised by no test at all and could have been silently inverted at any time -- turning four carriers from "refuses everything" into "accepts anything" on an endpoint where accepting anything lets a stranger revoke any consumer's consent. Closed by adding **************** which pins the default on a bare implementation and on the four real carriers relying on it. Re-running the same mutation against it: **************** **************** [an unimplemented carrier must never accept an unverifiable webhook] Expecting value to be false but was true **************** **************** [SinchTextProvider must fail closed] Expecting value to be false but was true Mutation 2 -- the one that actually targets the plan's named test: Twilio's blank-authToken guard changed from `return false` to `return true`. Observed: **************** **************** expected: 403 but was: 200 **************** **************** Expecting value to be false but was true Mutation 3 -- the parameter sort reversed (TreeMap natural order -> Comparator.reverseOrder()), to show the algorithm test is not vacuous: **************** **************** Expecting value to be true but was false **************** **************** expected: 200 but was: 403 All three mutations reverted. Full suite after revert: Tests run: 290, Failures: 0, Errors: 0, Skipped: 0 / BUILD SUCCESS Known gaps, deliberate and logged at runtime **************** * A verified inbound message is recognised and LOGGED BUT NOT RECORDED. The hand-off to SmsKeywordService is a marked TODO because that class arrives in Task 3. This endpoint suppresses nothing yet. * Org resolution scans active instances and matches the to-number with ContactCanon.phone. There is no number inventory -- the only ownership record is the single nullable FROM_PHONE_NUMBER on BulkTextProviderInstance, with no finder and no index. No match, or a match across two orgs, attributes the message to nobody and logs; guessing a tenant would record a revocation against the wrong one, which is worse than not recording it. Needs ADD5/MS3. * InboundSms carries no carrier timestamp: Twilio's inbound SMS webhook does not send one. Task 3's "consentedAt comes from the carrier event, never now()" is therefore not satisfiable for Twilio. Refs: **************** Task 2

Mabadiliko yote

Je, unaona nini kuhusu usafiri?

Kila moja ya hizi updates ardhi katika nafasi yako ya kazi moja kwa moja. Kuanza bure na kuangalia kukua wiki baada ya wiki.

Kuwa Huru MileleMtazamo wa bei