> ## 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>
