DEV Community

Cover image for SMS and WhatsApp sign-in codes for Supabase with MSG91
Aravind Govindhasamy
Aravind Govindhasamy

Posted on Originally published at starter.budeglobal.in on

SMS and WhatsApp sign-in codes for Supabase with MSG91

Phone sign-in is how many users in India expect to log in. Supabase Auth generates and checks the codes; you just need to deliver them. Supabase's built-in SMS providers don't cover every country's rules well, and none send WhatsApp, so Supabase lets you take over delivery with a Send SMS hook. This guide wires that hook to MSG91, which handles Indian SMS regulations and WhatsApp.

How it works

  1. A user enters their phone number; your frontend calls supabase.auth.signInWithOtp({ phone }).
  2. Supabase generates a six-digit code and calls your hook over HTTPS with the phone number and code.
  3. Your FastAPI endpoint verifies the request came from Supabase and sends the code through MSG91.
  4. The user types the code; supabase.auth.verifyOtp({ phone, token, type: "sms" }) signs them in.

1. Get your MSG91 auth key

Sign up at MSG91 and copy your authkey from the dashboard. Every API call sends it in an authkey header.

MSG91_AUTH_KEY=...

Enter fullscreen mode Exit fullscreen mode

2. For SMS in India: register on DLT

Indian telecom rules (TRAI) require every business SMS to be registered on a DLT platform run by the operators:

  1. Register your business as a Principal Entity and get an Entity ID.
  2. Register a header (the sender name users see, usually 6 letters).
  3. Register a content template for your code, for example: {#var#} is your Example sign-in code. It expires in 10 minutes.
  4. Add the approved template in MSG91 and note its template id.

Messages that don't match an approved template are dropped by the operators. Approval takes from a few hours to a few days, so start early.

3. For WhatsApp: an authentication template

Connect a WhatsApp Business number in MSG91 and create an Authentication template. Meta approves it, usually quickly. Authentication templates put the code in the message body and a "copy code" button.

MSG91_WHATSAPP_NUMBER=919876543210 # your integrated number, country code first

Enter fullscreen mode Exit fullscreen mode

4. Write the hook endpoint

Supabase signs hook requests with the Standard Webhooks scheme. The secret Supabase gives you looks like v1,whsec_<base64>; strip the prefix and verify the webhook-id, webhook-timestamp and webhook-signature headers:

import base64, hashlib, hmac, json, time
import httpx
from fastapi import Request
from fastapi.responses import JSONResponse

def verify(body: bytes, headers, secret: str) -> None:
    key = base64.b64decode(secret.removeprefix("v1,whsec_"))
    msg_id, ts = headers["webhook-id"], headers["webhook-timestamp"]
    if abs(time.time() - int(ts)) > 300:
        raise ValueError("stale")
    signed = f"{msg_id}.{ts}.".encode() + body
    expected = base64.b64encode(hmac.new(key, signed, hashlib.sha256).digest()).decode()
    if not any(hmac.compare_digest(expected, s.split(",", 1)[1])
               for s in headers["webhook-signature"].split()):
        raise ValueError("bad signature")

@app.post("/hooks/send-sms")
async def send_sms_hook(request: Request):
    body = await request.body()
    try:
        verify(body, request.headers, SEND_SMS_HOOK_SECRET)
    except (KeyError, ValueError):
        return JSONResponse({"error": {"http_code": 401, "message": "invalid"}}, 401)
    event = json.loads(body)
    phone, otp = event["user"]["phone"].lstrip("+"), event["sms"]["otp"]
    httpx.post(
        "https://control.msg91.com/api/v5/otp",
        params={"mobile": phone, "template_id": MSG91_OTP_TEMPLATE_ID},
        json={"OTP": otp},
        headers={"authkey": MSG91_AUTH_KEY},
        timeout=15,
    ).raise_for_status()
    return {}

Enter fullscreen mode Exit fullscreen mode

A 200 with an empty body tells Supabase the code was sent. For WhatsApp, send your approved template through MSG91's WhatsApp API instead, with the code as the body variable and the button value.

5. Turn it on in Supabase

  1. Authentication → Providers → Phone: enable phone sign-in.
  2. Authentication → Hooks: add a Send SMS hook of type HTTPS , URL https://api.example.com/hooks/send-sms.
  3. Generate the hook secret there and put it in your API's environment as SEND_SMS_HOOK_SECRET.

6. Test it

Sign in with your own number. If no message arrives, check, in order: your API's logs (did the hook arrive and verify?), MSG91's delivery reports (did it send?), and the DLT template (does the text match exactly?).

Cost note: every code is a paid message, so keep Supabase's rate limits on and consider a CAPTCHA on the form (Turnstile guide) to stop SMS pumping attacks.

Using it with FastAPI Supabase Starter

The hook endpoint, signature checks, MSG91 client and a delivery log are built in. Set MSG91_AUTH_KEY, SEND_SMS_HOOK_SECRET and (for WhatsApp) MSG91_WHATSAPP_NUMBER with OTP_CHANNEL=whatsapp, add the hook in Supabase, and map the otp template to your DLT template id or WhatsApp template name under Admin › Messaging. See Sign-in methods.

Top comments (0)