3.7 KiB
OTP delivery providers
telesrv owns OTP generation, storage, attempt limits, expiry, verification,
and consumption. A delivery provider receives an already-issued code and must
only deliver it. It must not generate a replacement code or decide whether an
authentication attempt succeeds.
Routing
TELESRV_PHONE_CODE_DELIVERY_PROVIDER=developmentpreserves the local fixed code.webhookgenerates random SMS codes for login, registration, login-email reset fallback, and phone changes.TELESRV_EMAIL_CODE_DELIVERY_PROVIDER=smtppreserves direct SMTP delivery.webhookhandles login-email, login-email setup, and login-email change.- One Webhook endpoint may handle both channels.
channelandpurposein the request select the downstream template/provider.
For an existing account, external delivery is additive: auth.sendCode and
auth.resendCode first commit the same code as a durable incoming message from
777000, then invoke the configured SMS or login-email provider. A provider
cannot replace or invalidate that App-code. A new phone and email setup/change
have no existing login dialog to receive the code, so those flows use only the
configured external provider.
Webhook v1 request
telesrv sends one POST request and does not follow redirects:
POST /v1/otp/deliveries HTTP/1.1
Content-Type: application/json
Accept: application/json
Idempotency-Key: otp_0193f0...
X-Telesrv-Timestamp: 1784275200
X-Telesrv-Signature: sha256=...
{
"version": "1",
"delivery_id": "otp_0193f0...",
"purpose": "login_email",
"channel": "email",
"recipient": "alice@example.test",
"code": "482913",
"expires_at": "2026-07-17T16:05:00Z",
"expires_in": 299,
"locale": "zh-CN"
}
Current purpose values are login_email, login_sms,
login_email_setup, login_email_change, and change_phone. Current channel
values are email and sms.
delivery_id is an opaque idempotency key. Replays of the same ID must not
send a second message. A resend that creates a new code has a new delivery ID.
When TELESRV_OTP_WEBHOOK_SECRET is non-empty, the signature is lowercase hex
HMAC-SHA256 over:
<X-Telesrv-Timestamp>.<exact raw JSON request body>
Response
An accepted request returns any 2xx response with this JSON shape:
{
"accepted": true,
"message_id": "provider-message-123"
}
204 No Content is also accepted. Other 2xx responses must explicitly contain
"accepted": true; a missing or malformed acknowledgement is treated as an
unknown outcome because the provider may already have sent the code.
An explicit rejection may use either a non-2xx status or accepted: false:
{
"accepted": false,
"error_code": "RECIPIENT_INVALID",
"retryable": false
}
The response body is capped at 64 KiB. For a flow without a durable 777000 fallback, an explicit rejection invalidates only the code attempt that triggered that request. A transport error or invalid successful acknowledgement preserves the code and returns its hash because the provider may already have sent it. For an existing-account login, any provider failure is reported but does not fail the RPC or invalidate the code: the durable 777000 copy remains the authoritative fallback.
Webhook logs contain the opaque delivery ID, purpose, channel, status, and transport error only. The code and recipient are not logged.
A runnable standard-library receiver is available at
cmd/otpwebhook-example. It includes
signature/timestamp validation, request limits, idempotency, health checking,
and graceful shutdown. Its delivery function is intentionally a no-op adapter
and must be replaced with the user's email/SMS API call.