Twilio One-Time Passcode Verification Service
A reusable service that generates a one-time passcode, sends it via Twilio SMS, stores a salted digest with expiry/attempt limits in Dataverse, and exposes a verify endpoint that checks the submitted code. Other apps and flows call it to add SMS-based verification (login, high-risk actions, callbacks) without building OTP logic themselves.
Provided as-is, without warranty of any kind. Review and test each pattern in a non-production environment before deploying it to live automations. See our Terms.
Overview
This is the as-built FlowLibs reference for the Twilio One-Time Passcode (OTP) Verification Service - a reusable, two-flow SMS verification service. A Request endpoint generates a 6-digit passcode, stores a salted digest with expiry/attempt limits in Dataverse, and texts the code via Twilio. A Verify endpoint checks a submitted code against the stored digest and enforces expiry, single-use, and attempt limits. Other apps and flows call these HTTP endpoints to add SMS verification (login, high-risk actions, callbacks) without reimplementing OTP logic.
Both flows ship Off. Going live requires only authorizing the connections and setting environment-variable values.
Use Case
Apps repeatedly need a quick SMS verification step and OTP is easy to get subtly wrong (expiry, replay, brute force). This service standardizes it behind two endpoints, keeps the passcode out of every calling app, persists an auditable Dataverse record per request, and carries a correlationId so issuance and verification can be traced end-to-end.
Flow Architecture
Flow 1: OTP Request (HTTP)
RequestBody: phoneNumber (E.164, required), purpose (optional)
Generate code, salt, and digest
Compose + Initialize VariableRandom 6-digit code, per-code salt (guid), salted base64 digest, expiry timestamp
Create OTP Record
Dataverse CreateRecordStores digest, salt, expiry, status=Pending, attempts=0
Send OTP SMS + Respond
Twilio SendMessage + ResponseTexts the code, returns requestId (row id) + expiresAt + correlationId; never the code
Flow 2: OTP Verify (HTTP)
RequestBody: requestId (row id from Flow 1), code
Get OTP Record + recompute digest
Dataverse GetItem + Initialize VariableFetches the stored record and recomputes base64(stored salt + submitted code)
Validation decision tree
Nested IfIs Pending -> Not Expired -> Attempts OK -> Code Match; each terminal branch returns a Response (already_used / expired / locked / invalid_code / valid)
Verified or Invalid
Dataverse UpdateRecord + ResponseOn match: single-use, mark Verified, return valid:true. On miss: increment attempts, return invalid_code + attemptsRemaining
Environment Variables
| Schema name | Type | Default | Description |
|---|---|---|---|
| flowlibs_TwilioFromNumber | String | +15551234567 | Verified Twilio sender number (E.164) |
| flowlibs_OtpExpiryMinutes | String | 5 | Minutes a passcode stays valid |
| flowlibs_OtpMaxAttempts | String | 3 | Failed attempts before lockout |
| flowlibs_OtpMessageTemplate | String | Your verification code is {code}. It expires in {minutes} minutes. Do not share this code with anyone. | SMS body template ({code}/{minutes} placeholders) |
| flowlibs_OtpTableName | String | flowlibs_smsotps | Dataverse entity set name of the OTP table |
Connectors & Connections
| Connector | API name | Actions used |
|---|---|---|
| Twilio | shared_twilio | SendMessage |
| Microsoft Dataverse | shared_commondataserviceforapps | CreateRecord GetItem UpdateRecord |
Note — All connections are referenced as solution connection references; the flow is portable between environments as long as a connection is mapped at import time.
Customization Guide
Almost every realistic variant of this flow can be implemented by changing environment variable values. A few cases require small edits inside the flow definition — those are called out explicitly below.
- Production-grade hashing
- Replace the salted base64 digest with an HMAC-SHA256 computed via an inline HTTP call to an Azure Function; the salt+digest+recompute+compare pattern stays identical.
- Twilio Verify
- Swap the custom store for Twilio's managed Verify API for built-in delivery and fraud controls.
- Endpoint security
- Add a shared-secret header check (or APIM/Entra) on both Request triggers before go-live; the demo endpoints are open.
- Rate limiting
- Add a per-number send throttle (e.g. a Dataverse ListRecords count over a window) in the Request flow.
- Voice fallback / WhatsApp
- Reuse the same Twilio SendMessage op with a whatsapp: prefix, or add a voice-call fallback if SMS fails.
- Lockout window
- Add a timed unlock instead of a permanent Locked status.
Key Expressions
The flow is intentionally light on Power Fx / WDL gymnastics — the heaviest expressions are the branch-name concatenation and the approval outcome check. They are listed below in the order they appear in the flow.
EXPR.01Generate code
Always 6 digits
EXPR.02Salted digest
At-rest digest (salt + code)
EXPR.03Expiry timestamp
Absolute UTC expiry
EXPR.04Expiry check (Verify)
True while not expired
EXPR.05Attempt check
True while under the attempt cap
EXPR.06Code match
Recomputed digest equals stored digest
EXPR.07Attempts remaining
Returned on an invalid attempt
EXPR.08Message body
Token substitution in the SMS template
Customize & download
Generate a ready-to-import copy of this solution with your environment-variable values baked in — available on Base, Pro, or Team.
Upgrade to customize
Comments
Sign in to join the conversation.
Sign inNo comments yet. Be the first to share your experience with this flow.