superun SMS Provider
superun 雲計算 預設已經為你啟用手機登入並配置好 send_sms 鉤子,可直接使用;如果希望走自家簡訊通道,也可以配置自訂 Webhook 接管。
客戶端整合
無論使用預設 Provider 還是自訂 Provider,客戶端代碼完全一致:
import { supabase } from "@/integrations/supabase/client";
// 發送 OTP
async function sendOTP(phone: string) {
const { data, error } = await supabase.auth.signInWithOtp({ phone });
if (error) throw new Error(error.message);
return data;
}
// 驗證 OTP
async function verifyOTP(phone: string, token: string) {
const { data, error } = await supabase.auth.verifyOtp({
phone,
token,
type: "sms",
});
if (error) throw new Error(error.message);
return data;
}
使用流程:
- 用戶點擊「發送驗證碼」時調用
sendOTP(phone),平台或你的自訂 Webhook 會收到事件並下發簡訊
- 用戶填入收到的驗證碼後調用
verifyOTP(phone, token) 完成驗證
預設 Provider(推薦)
開通 superun 雲計算 後手機登入已自動啟用,無需任何額外操作,適合絕大多數場景,零維運成本。
自訂 Provider(進階)
適用場景:希望使用自家簡訊通道、按國家/電信業者分流、出海合規要求自建發送鏈路等。
啟用後,Supabase 會把 send_sms 事件簽名轉發到你提供的 Webhook,由你的服務調用最終的簡訊通道(阿里雲/騰訊雲/Twilio 等)下發驗證碼。
1. 在控制台設定自訂 Hook
進入專案 superun雲計算 → 身分驗證設定 → 電話 Provider,展開 自訂 SMS Hook 卡片:
填寫兩個欄位:
- Hook URI:你部署的 Webhook 接口位址,必須是
https://
- Hook Secret:用於驗證 Webhook 請求簽名的密鑰。點擊右側 隨機產生 按鈕產生符合
v1,whsec_<base64> 格式的密鑰,再用 複製 按鈕把它保存到你自己的服務設定裡
兩個欄位必須同時填寫或同時留空。留空則繼續使用平台預設 Hook,要回退到預設 Provider 直接清空兩個欄位並儲存即可。
儲存後,下一次手機登入的 OTP 事件就會被轉發到你的 Webhook。
2. 實作 Webhook 服務
下面是一個最小化的 Express 範例,演示如何接收 Supabase 的 send_sms 事件、驗證簽名、提取手機號與驗證碼,再轉交給你自己的簡訊通道。
簽名驗證使用的是 Supabase 官方推薦的 standardwebhooks 套件(與 superun 平台預設 Hook 使用同一套規範),先安裝相依套件:
npm install express standardwebhooks
# 如果使用 TypeScript
npm install -D @types/express
import express from "express";
import { Webhook } from "standardwebhooks";
const app = express();
// 必須使用 raw body:standardwebhooks 是基於原始請求體計算簽名的,
// 先被 express.json() 反序列化後會導致驗證失敗
app.use("/webhook/send-sms", express.raw({ type: "application/json" }));
// 用控制台填寫的 Secret 初始化(去掉 v1,whsec_ 前綴)
const SECRET = process.env.SMS_HOOK_SECRET!.replace(/^v1,whsec_/, "");
const webhook = new Webhook(SECRET);
app.post("/webhook/send-sms", async (req, res) => {
try {
const verified = webhook.verify(
req.body,
req.headers as Record<string, string>
) as {
user: { phone?: string; new_phone?: string };
sms: { otp: string; phone?: string };
};
const { user, sms } = verified;
let phone = sms?.phone || user?.new_phone || user?.phone || "";
const otp = sms?.otp;
if (!phone || !otp) {
return res.status(400).json({ error: "missing phone or otp" });
}
// Supabase 通常會帶 + 號傳過來;按下游通道需要決定是否去掉
if (phone.startsWith("+")) phone = phone.slice(1);
// === 這裡替換成你自己的簡訊通道調用 ===
await sendSmsViaYourGateway(phone, otp);
return res.status(200).json({ success: true });
} catch (err) {
console.error("send-sms webhook failed", err);
return res.status(401).json({ error: "invalid signature or payload" });
}
});
async function sendSmsViaYourGateway(phone: string, code: string) {
// TODO: 接入你的簡訊服務(阿里雲、騰訊雲、Twilio 等)
}
app.listen(3000, () => console.log("Webhook listening on :3000"));
要點:
- 必須使用 raw body(
express.raw),先 JSON parse 會導致簽名驗證失敗;同一路由上不要再掛 express.json(),它會把 body 解析成物件、覆蓋 raw 位元組
Secret 傳給 standardwebhooks 時務必去掉 v1,whsec_ 前綴
- 簽名相關的 header(
webhook-id / webhook-timestamp / webhook-signature)由 Express 統一規範化為小寫,standardwebhooks 內部也按小寫讀取,直接傳 req.headers 即可;如果中間走了反向代理或 APM,要確認它們沒有改寫 body 位元組或丟掉這三個 header
- 該接口必須以 HTTPS 對外暴露(控制台也會驗證
https://)
- 驗證失敗一律回傳 4xx,避免 Supabase 誤判已下發
3. 聯調驗證
完成上面兩步後,在你的應用裡走一遍 signInWithOtp(phone),確認:
- Webhook 服務收到請求且簽名驗證通過
- 你的簡訊通道確實下發了驗證碼
- 用戶輸入驗證碼後
verifyOtp 回傳成功
運作方式
無論是預設還是自訂 Provider,鏈路都一樣:
- 客戶端調用
signInWithOtp({ phone })
- Supabase 生成 OTP 並觸發
send_sms 鉤子
- 鉤子帶簽名 POST 到 Webhook 位址(預設 = superun Gateway;自訂 = 你部署的服務)
- Webhook 驗證簽名後調用底層簡訊通道下發驗證碼
- 用戶輸入驗證碼,客戶端
verifyOtp 完成驗證
預設 Provider 下的 Edge Function、簽名密鑰、SMS 閘道整合都由 superun 雲計算 自動維護,你只需要關心客戶端流程。自訂 Provider 下,你額外負責 Webhook 服務與簡訊通道的穩定性。