> ## Documentation Index
> Fetch the complete documentation index at: https://docs.superun.com/llms.txt
> Use this file to discover all available pages before exploring further.

# superun SMS Provider

> 在 superun 雲計算 中使用 SMS 手機驗證：可直接使用平台預設 Provider，也可接入自家簡訊通道作為自訂 Provider

# superun SMS Provider

superun 雲計算 預設已經為你啟用手機登入並配置好 `send_sms` 鉤子，可直接使用；如果希望走自家簡訊通道，也可以配置自訂 Webhook 接管。

## 客戶端整合

無論使用預設 Provider 還是自訂 Provider，客戶端代碼完全一致：

```typescript theme={null}
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;
}
```

使用流程：

1. 用戶點擊「發送驗證碼」時調用 `sendOTP(phone)`，平台或你的自訂 Webhook 會收到事件並下發簡訊
2. 用戶填入收到的驗證碼後調用 `verifyOTP(phone, token)` 完成驗證

## 預設 Provider（推薦）

開通 superun 雲計算 後手機登入已自動啟用，無需任何額外操作，適合絕大多數場景，零維運成本。

## 自訂 Provider（進階）

適用場景：希望使用自家簡訊通道、按國家/電信業者分流、出海合規要求自建發送鏈路等。

啟用後，Supabase 會把 `send_sms` 事件簽名轉發到你提供的 Webhook，由你的服務調用最終的簡訊通道（阿里雲/騰訊雲/Twilio 等）下發驗證碼。

### 1. 在控制台設定自訂 Hook

進入專案 **superun雲計算 → 身分驗證設定 → 電話 Provider**，展開 **自訂 SMS Hook** 卡片：

<img src="https://b.yzcdn.cn/public_files/199cc4532f599adeaaf16845891757ef.jpg" alt="superun雲計算" width={800} />

<img src="https://b.yzcdn.cn/public_files/e24558dc24d82b54fbca2a95f8826c45.jpg" alt="電話登入" width={800} />

填寫兩個欄位：

* **Hook URI**：你部署的 Webhook 接口位址，必須是 `https://`
* **Hook Secret**：用於驗證 Webhook 請求簽名的密鑰。點擊右側 **隨機產生** 按鈕產生符合 `v1,whsec_<base64>` 格式的密鑰，再用 **複製** 按鈕把它保存到你自己的服務設定裡

<img src="https://b.yzcdn.cn/public_files/caa0b918722ed2735b6c4e05ad075aae.jpg" alt="設定webhook" width={800} />

<Note>
  兩個欄位必須同時填寫或同時留空。留空則繼續使用平台預設 Hook，要回退到預設 Provider 直接清空兩個欄位並儲存即可。
</Note>

儲存後，下一次手機登入的 OTP 事件就會被轉發到你的 Webhook。

### 2. 實作 Webhook 服務

下面是一個最小化的 Express 範例，演示如何接收 Supabase 的 `send_sms` 事件、驗證簽名、提取手機號與驗證碼，再轉交給你自己的簡訊通道。

簽名驗證使用的是 Supabase 官方推薦的 [`standardwebhooks`](https://www.npmjs.com/package/standardwebhooks) 套件（與 superun 平台預設 Hook 使用同一套規範），先安裝相依套件：

```bash theme={null}
npm install express standardwebhooks
# 如果使用 TypeScript
npm install -D @types/express
```

```typescript theme={null}
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)`，確認：

1. Webhook 服務收到請求且簽名驗證通過
2. 你的簡訊通道確實下發了驗證碼
3. 用戶輸入驗證碼後 `verifyOtp` 回傳成功

## 運作方式

無論是預設還是自訂 Provider，鏈路都一樣：

1. 客戶端調用 `signInWithOtp({ phone })`
2. Supabase 生成 OTP 並觸發 `send_sms` 鉤子
3. 鉤子帶簽名 POST 到 Webhook 位址（預設 = superun Gateway；自訂 = 你部署的服務）
4. Webhook 驗證簽名後調用底層簡訊通道下發驗證碼
5. 用戶輸入驗證碼，客戶端 `verifyOtp` 完成驗證

<Note>
  預設 Provider 下的 Edge Function、簽名密鑰、SMS 閘道整合都由 superun 雲計算 自動維護，你只需要關心客戶端流程。自訂 Provider 下，你額外負責 Webhook 服務與簡訊通道的穩定性。
</Note>
