Xodian文档 首页
Sandbox →

Xodian · API v1

Xodian 文档

Xodian 完整手册:快速开始、核心概念、API 参考、组件、令牌验证、Webhook、沙盒以及用户指南。

概览

Xodian 让你的应用确认用户是使用正版手机的一个真实的人,而无需知道他是谁。不需要自拍,不需要上传证件,也不需要个人信息。

用户在 omni 应用中确认。你的后端会收到一个签名的 id_token(ES256 JWT),其中包含:

  • human:这是使用未经修改的正版手机的真实用户
  • sub:只有你的应用能看到的此人的私密 ID,每次访问都相同
  • level:证据强度(1 = 设备,2 = 长期使用)
  • uniqueness:此人对你来说是新用户(new)、回访用户(returning),还是存在冲突(contested)

你会得到

每個應用程式、每位真實人士一個穩定 ID。同一支手機上的 50 個錢包會收斂為一個 ID,換手機後該錢包原有的 ID 仍會保留。只有重新安裝並換新錢包的情況無法回溯連結,此時會標記為 new_device 送出 — 在重要之處請要求等級 2。

你永远不会得到

姓名、面部、证件、手机号、设备型号、余额或活动记录。钱包地址只有在你请求且用户同意时才会提供。

适合的场景

空投和奖励活动(每人一次)、游戏账号和试用、社区投票,以及放在完整 KYC 之前的低成本机器人过滤。

不适合的场景

Xodian 不是身份验证。它不会告诉你用户的姓名、年龄或国家,本身也不能满足 KYC/AML 义务。

快速开始(10 分钟)

你需要一台能保存密钥的服务器,以及用来显示按钮的网页或应用。沙盒免费,无需注册。

  1. 获取测试密钥

    在首页的沙盒部分或通过 API 获取。密钥只显示一次,请保存在服务器上。

    curl -X POST https://xodian.xode.net/v1/test/clients \
      -H "Content-Type: application/json" \
      -d '{"name":"Acme Airdrop","domain":"acme.example"}'
    
    # → {"clientId":"rpt_3f9a…","clientSecret":"osk_rpt_3f9a…_…",
    #    "test":true,"baseUrl":"https://xodian.xode.net","dailyQuota":500}
  2. 在服务器上创建会话

    reference 是你为这次尝试设置的 ID(例如领取 ID),会作为令牌中的 nonce 原样返回。

    curl -X POST https://xodian.xode.net/v1/sessions \
      -H "Authorization: Bearer $XODIAN_SECRET" \
      -H "Content-Type: application/json" \
      -d '{"scope":["human"],"minLevel":1,"reference":"signup-42"}'
    {
      "sessionId": "BQuIzVWtfYrpFA4_N4ZgzpLW",
      "pollToken": "CI_8JleZxedaAoDLo7Wg46vB",
      "expiresAt": 1791251375,
      "url": "https://xodian.xode.net/s/BQuIzVWtfYrpFA4_N4ZgzpLW",
      "deeplink": "xode://id?s=BQuIzVWtfYrpFA4_N4ZgzpLW",
      "scope": ["human"],
      "minLevel": 1,
      "test": true
    }

    把整个对象发给浏览器即可,其中不含任何密钥。

  3. 显示按钮

    组件在电脑上显示二维码,在手机上显示"在 omni 中继续"按钮,并轮询状态、回调结果。

    <script src="https://xodian.xode.net/sdk.js"></script>
    <div id="verify"></div>
    <script>
      Xodian.button("#verify",
        () => fetch("/api/xodian/start", { method: "POST" }).then(r => r.json()),
        { onApproved: (s) => fetch("/api/xodian/finish?session=" + s.sessionId) });
    </script>
  4. 在沙盒中响应

    测试会话会在组件内显示模拟面板(批准、拒绝、未通过、过期),也可以调用模拟器 API。

  5. 在服务器上获取并验证令牌

    用密钥读取会话,再用 JWKS 验证 idToken。参见验证令牌。

正式上线时代码不变,只需换成正式密钥(rp_… 而非 rpt_…)。生产环境中请拒绝带有 "test": true 的令牌。

核心概念

会话

一次验证尝试。由你的服务器创建,用户需在 5 分钟内(expiresAt)在 omni 中响应。每个会话最终只会处于一种状态:

status含义你该做什么
pending等待用户响应保持二维码或按钮可见
approved已以不低于 minLevel 的等级通过验证在服务器上获取令牌
rejected用户尝试了但未达标,见 reason显示中性提示;omni 会私下告诉用户如何达标
denied用户拒绝了允许稍后重试
expired超时无人响应创建新会话

保证等级

level要求适用于
1 · device(设备)钱包绑定到硬件密钥(StrongBox、TEE 或 Secure Enclave);通过 Google Play Integrity 或 Apple App Attest,且近期重新核验过。拒绝模拟器、已 root 设备和被修改的应用。注册、试用、机器人过滤
2 · established(长期使用)等级 1,加上账号至少 14 天、7 天的已驗證活動(收到帶有硬體金鑰簽章而非僅錢包簽章的心跳的日子)、近 30 天未更换设备。空投、发放奖励、投票

minLevel 按会话设置。低于它时你只会收到 rejected,不会收到等级或其他信号。

一人一帳戶是哪個等級?

等級 1 回答「現在是真實手機」。適合機器人過濾、試用與註冊。

若您付費購買的是唯一性,請要求等級 2 — 空投、撥款、投票。在 Android 上硬體金鑰會隨應用程式資料一併刪除,因此重新安裝加上新錢包會呈現為未曾見過的手機。我們不隱藏,而是以 new_device 回報。等級 2 可封閉此情形:在同一支手機上為第二個錢包累積已驗證天數會造成裝置變更,而 30 天內的裝置變更無法通過等級 2。iOS 的金鑰留在鑰匙串中,因此會回傳 returning 或 contested。

应用专属 ID(sub)

同一个人在不同应用中得到不同的 sub,因此合作方无法通过合并数据来追踪一个人。在你的应用内它是固定的:同一个人每次都得到相同的 sub,即使把钱包迁移到新手机也不变。一部手机上的多个钱包得到同一个 sub。请用 sub 作为"每人一次"的判定依据。

唯一性(uniqueness)

值含义
new此人首次在你的应用中验证
returning之前见过;与上次相同的 sub
contested钱包迁移到了一部已在你的应用中以另一个人身份验证过的手机。由你决定:接受、暂缓审核或拒绝。

权限范围(scope)

human 始终包含。如需用户的 XODE 钱包地址,添加 wallet;只有用户在 omni 中开启共享时才会提供,且你的密钥必须已开通此权限。

reference 与 nonce

你传入的 reference(最多 200 个字符)会作为令牌的 nonce 返回。务必核对,防止为一次尝试签发的令牌被用于另一次尝试。

API 参考

基础 URL 为 https://xodian.xode.net,请求和响应均为 JSON。服务器调用使用客户端密钥认证:

Authorization: Bearer osk_<clientId>_<random>
方法路径认证用途
POST/v1/sessions密钥创建会话
GET/v1/sessions/{id}密钥状态;通过后包含令牌
GET/v1/sessions/{id}/status?t=轮询令牌浏览器安全的状态查询(从不返回令牌)
GET/v1/.well-known/jwks.json公开签名公钥
GET/v1/.well-known/configuration公开发现文档
POST/v1/test/clients无创建沙盒密钥
POST/v1/test/sessions/{id}/simulate测试会话响应测试会话

POST /v1/sessions

字段类型默认值
scopestring[]["human"]human,可选 wallet
minLevel1 | 21你接受的最低等级(不超过密钥上限)
referencestring""你的关联 ID,作为 nonce 返回
{
  "sessionId": "BQuIzVWtfYrpFA4_N4ZgzpLW",
  "pollToken": "CI_8JleZxedaAoDLo7Wg46vB",
  "expiresAt": 1791251375,
  "url": "https://xodian.xode.net/s/BQuIzVWtfYrpFA4_N4ZgzpLW",
  "deeplink": "xode://id?s=BQuIzVWtfYrpFA4_N4ZgzpLW",
  "scope": ["human"],
  "minLevel": 1,
  "test": true
}

url 是二维码中的地址;deeplink 在同一部手机上直接打开 omni。请把 sessionId、pollToken、url、deeplink 和 expiresAt 传给浏览器。

GET /v1/sessions/{id}

仅限服务器调用。返回状态;为 approved 时还会返回 idToken、sub 和 level。只有创建该会话的客户端才能读取。

curl https://xodian.xode.net/v1/sessions/BQuIzVWtfYrpFA4_N4ZgzpLW \
  -H "Authorization: Bearer $XODIAN_SECRET"

# approved →
{
  "sessionId": "BQuIzVWtfYrpFA4_N4ZgzpLW",
  "status": "approved",
  "expiresAt": 1791251375,
  "idToken": "eyJhbGciOiJFUzI1NiIs…",
  "sub": "oid_9f3c1a8be41d…",
  "level": 1
}

# rejected →
{ "sessionId": "…", "status": "rejected", "reason": "insufficient_level", "expiresAt": … }

GET /v1/sessions/{id}/status

供浏览器使用。会话专属的 pollToken 只能看到状态,以及最终状态的原因。组件会每 1 到 3 秒替你调用一次。

GET https://xodian.xode.net/v1/sessions/{sessionId}/status?t={pollToken}

{ "sessionId": "…", "status": "pending", "expiresAt": 1791251375 }

发现文档与密钥

GET https://xodian.xode.net/v1/.well-known/configuration

{
  "service": "Xodian",
  "issuer": "https://xodian.xode.net",
  "jwks_uri": "https://xodian.xode.net/v1/.well-known/jwks.json",
  "sessions_endpoint": "https://xodian.xode.net/v1/sessions",
  "scopes_supported": ["human", "wallet"],
  "levels": { "1": "device", "2": "established" },
  "id_token_signing_alg_values_supported": ["ES256"],
  "session_ttl_seconds": 300
}

密钥为 ES256(P-256)。轮换密钥后,旧公钥会在 JWKS 中保留一段时间,因此轮换前刚签发的令牌仍可验证。请缓存 JWKS,遇到未知的 kid 时重新获取。

错误

HTTP何时出现
400未知的 scope、minLevel 超出范围、请求体格式错误
401缺少密钥、格式错误或无效
403你的密钥未开通 wallet 权限
404会话不存在或属于其他客户端(轮询令牌错误也返回 404)
409会话已结束
429已达每日验证配额,或今天创建的测试密钥过多

错误为 JSON {"detail": "…"},其中 detail 是消息文本,或包含 code 和 message 的对象。

网页组件(sdk.js)

一个无依赖的小脚本,适用于任何页面和任何框架。

<script src="https://xodian.xode.net/sdk.js"></script>

Xodian.button(target, getSession, options)

在 target(选择器或元素)中渲染"Verify with Xodian"按钮。点击后等待 getSession(),该函数必须返回从你的后端获取的会话对象,然后打开弹窗。

<script src="https://xodian.xode.net/sdk.js"></script>
<div id="verify"></div>
<script>
  Xodian.button("#verify",
    () => fetch("/api/xodian/start", { method: "POST" }).then(r => r.json()),
    { onApproved: (s) => fetch("/api/xodian/finish?session=" + s.sessionId) });
</script>

Xodian.open(session, options)

用已有的会话打开弹窗。返回 { close() }。

// Your own button, your own flow:
const session = await fetch("/api/xodian/start", { method: "POST" }).then(r => r.json());

const modal = Xodian.open(session, {
  onApproved: (s) => finish(s.sessionId),     // fetch the token on YOUR server
  onRejected: (s) => explain(s.reason),
  onDenied:   () => {},
  onExpired:  () => {},
  onClose:    () => {},                       // closed by the user
});
// modal.close() closes it from code.

选项

选项
onApproved(s)已通过。s 是公开状态;令牌请在服务器上获取。
onRejected(s)未达标,原因见 s.reason。
onDenied(s) · onExpired(s)被拒绝或已超时。
onClose()用户关闭了弹窗。
onError(e)仅 button:getSession() 失败。
label仅 button:按钮文字。
text自定义或翻译弹窗文案(见下文)。
autoCloseMs成功状态的停留时间(默认 1800)。
persona仅沙盒:模拟用户的名字。
baseUrl指定 API 主机(通常自动取自脚本地址)。

文案与语言

弹窗默认是英文。传入 text 即可翻译或改成你的语气。键名:title、subtitle、step1、step1m(移动端)、step2、step3、openApp、noApp、secured、okTitle、okBody、deniedTitle、deniedBody、rejTitle、rejBody、expTitle、expBody、close。未提供的键显示英文。

Xodian.open(session, {
  text: {
    title: "사람 인증",
    subtitle: "실제 사람인지 확인해요. 이름·번호·지갑은 공유되지 않아요.",
    step1: "omni 앱으로 스캔", step2: "공유 내용을 확인하고 승인", step3: "완료",
    close: "닫기",
  },
});

原生应用

// iOS / Android / Flutter: no QR needed on the same phone.
// Open the deeplink from the session your backend created:
openURL(session.deeplink)          // xode://id?s=…

// omni asks the person, then returns them. Your app polls your backend,
// which reads GET /v1/sessions/{id} with the secret.
扫码后打开的页面(/s/<id>)会跟随手机语言显示:英语、韩语、日语或中文。

Xodian Connect(錢包)

Connect 是作為 web3 閘道的 Xodian:您的網站無需瀏覽器擴充功能即可連接使用者的 XODE 錢包並請求簽署。復原助記詞不會離開 omni 應用程式,且每一項請求都會在該應用中先向使用者顯示,才會簽署。

流程如下:您的後端開啟工作階段,使用者在 omni 中核准(桌面掃 QR,手機用深層連結),您的頁面取得一次性的 connect 權杖,之後便以該權杖請求簽署。

開啟工作階段

正式用戶端從後端攜帶用戶端密鑰開啟工作階段,只把 sessionId 與 pollToken 交給瀏覽器:

POST https://xodian.xode.net/v1/connect/sessions
Authorization: Bearer osk_<clientId>_<secret>
Origin: https://your-domain.example
Content-Type: application/json

{"clientId":"rp_…","scope":["human","wallet"],"minLevel":1}

Origin 不是憑證

Origin 標頭約束瀏覽器,卻不約束 HTTP 用戶端。若沒有密鑰,任何人都能送出您的網域與(公開的)clientId,以您的名義開啟工作階段,並在有人核准的瞬間取得 connect 權杖。因此正式用戶端必須提供密鑰,否則會收到 403 secret_required。Origin 規則仍在其上繼續適用 — 密鑰說明是誰,Origin 說明來自何處。

沙箱金鑰保留僅瀏覽器的路徑。自助申請的 rpt_ 金鑰可以直接從頁面開啟工作階段,透過您註冊的網域或 http://localhost,因為在那裡無利可圖:真實手機會拒絕沙箱工作階段,因此永遠無法觸及真實錢包。此類工作階段在同意畫面上顯示為未驗證,正式用戶端未帶密鑰所開的工作階段亦同。

SDK

<script src="https://xodian.xode.net/connect.js"></script>

XodianConnect.init({ clientId: 'rp_…', chainId: 3417 });
const { address, evmAddress, sub, level } = await XodianConnect.connect();
XodianConnect.disconnect();

連線資訊(權杖、位址、配對式 sub、等級)保存在頁面自身的 localStorage,有效 30 天。簽署呼叫回傳 401 表示已撤銷或過期,SDK 會清除並重新連線。

探索有兩條路。對 EVM 程式庫,提供者依 EIP-6963 自我宣告為 XODE omni(rdns: net.xode.omni),因此會出現在 wagmi、RainbowKit 與 viem 的清單中,而不必爭奪 window.ethereum — 只有當您想填入該位置且它空著時才使用 init({ injectEthereum: true })。對 polkadot-js 應用,錢包位於 window.injectedWeb3.xodian,提供 enable()、accounts.get() 與 signer。

提供者方法

方法回應
eth_requestAccounts · eth_accounts錢包的 0x 位址,由與其 XODE 帳戶相同的復原助記詞衍生
eth_chainId · net_version告知頁面的鏈。預設為 XODE 自有的 EVM 層(3417),以 init({ chainId }) 設定您的鏈
wallet_switchEthereumChain接受任何鏈。連線並不綁定於某一條鏈 — 訊息簽署與鏈無關,而交易自帶 chainId
personal_sign由 0x 帳戶產生的 EIP-191 簽章。手機需 omni 1.2.1 或更新版本
其他4200 不支援的方法 — 包含尚未提供的 eth_sendTransaction

值得處理的錯誤

代碼意義
4001使用者拒絕或關閉了對話框。請顯示為取消,而非失敗
4100此連線沒有 0x 位址 — 由較舊的 omni 版本建立。請重新連線
4200不支援的方法
-32602參數無效(位址錯誤、鏈 id 格式有誤)

omni 會拒絕什麼,以及為什麼

簽署畫面就是安全邊界,因此凡是無法向使用者說明的,一律拒絕。以下是預期行為,請視為正確:

  • 屬於 XODE 自身保留字串的訊息 — 任何以 xode- 開頭者。這些字串用於讓錢包向 XODE 自身認證,網站不應能要求簽署。
  • omni 無法從原始位元組確認其 pallet 與呼叫索引的呼叫。此判斷在詢問解碼器之前完成。
  • 批次包裝。要讀出其中內容需要完整的鏈 metadata,而手機並未攜帶,因此寧可拒絕也不憑信任核准。
  • 凡不是 omni 自行從位元組重建的那兩種轉帳的餘額轉帳,一律拒絕。這是擋下「全額轉出」之類的方式。
  • 含有 bidi 覆寫或零寬字元的文字 — 以 hex 顯示並附警告,因為這樣的一行讀起來與其內容不同。
  • 過長的訊息,在使用者展開之前。超過約 400 個字元或 8 行即會折疊且核准按鈕保持停用,因此藏在畫面下方的要求無法以一次輕觸完成簽署。請讓登入訊息保持簡短。

收據

每一次簽署決定都會記錄於 Sentinel 帳本並錨定於 XODE,與驗證相同。拒絕會附上錢包簽章,因此寫著使用者已拒絕的收據確實代表使用者;未附簽章而關閉的請求則記錄為取消。

上限

連線30 天,使用者可隨時在 omni 中撤銷
簽署請求5 分鐘內回應;每個連線每 5 分鐘最多 30 筆
訊息大小一般訊息 1 KB,EIP-191 訊息 8 KB

验证令牌

请务必在服务器上验证,不要相信浏览器报告的状态。

声明(claims)

{
  "iss": "https://xodian.xode.net",
  "aud": "rp_acme",
  "sub": "oid_9f3c1a8be41d02c7aa50e07b",
  "iat": 1791251090, "exp": 1791251690,
  "jti": "BQuIzVWtfYrpFA4_N4ZgzpLW",
  "nonce": "signup-42",
  "human": true,
  "level": 2, "level_name": "established",
  "uniqueness": "new",
  "verified_at": 1791251090,
  "receipt": "1791251090-a1b2c3d4",
  "wallet": "XqF4…"          // only with the wallet scope AND the person's consent
}
声明
iss始终为 https://xodian.xode.net
aud你的客户端 ID
sub此人在你的应用中的专属 ID
exp签发后 10 分钟;请立即获取并验证
jti会话 ID;保存它以确保每个令牌只能使用一次
nonce你的 reference
level、level_name1 设备 · 2 长期使用
uniquenessnew / new_device / returning / contestednew · returning · contested
receipt已锚定上链的判定记录的句柄(正式密钥)
wallet仅在有 wallet 权限且用户同意时出现
test沙盒令牌为 true;正式令牌中不存在

检查清单

  1. 签名为 ES256 且可通过 JWKS 验证
  2. iss 为 https://xodian.xode.net,aud 为你的客户端 ID
  3. exp 尚未过期
  4. nonce 等于创建会话时的 reference
  5. jti 未被使用过
  6. 生产环境中不存在 test
  7. level 不低于该操作所需的等级
import { createRemoteJWKSet, jwtVerify } from "jose";

const JWKS = createRemoteJWKSet(
  new URL("https://xodian.xode.net/v1/.well-known/jwks.json"));

export async function finish(sessionId, expectedReference) {
  const s = await fetch(`https://xodian.xode.net/v1/sessions/${sessionId}`, {
    headers: { Authorization: `Bearer ${process.env.XODIAN_SECRET}` },
  }).then(r => r.json());
  if (s.status !== "approved") return { ok: false, reason: s.reason || s.status };

  const { payload } = await jwtVerify(s.idToken, JWKS, {
    issuer: "https://xodian.xode.net",
    audience: process.env.XODIAN_CLIENT_ID,
    algorithms: ["ES256"],
  });
  if (payload.nonce !== expectedReference) throw new Error("token is for another session");
  if (payload.test && process.env.NODE_ENV === "production") throw new Error("sandbox token");
  await db.usedTokens.insertUnique(payload.jti);          // one use per token
  return { ok: true, person: payload.sub, level: payload.level,
           contested: payload.uniqueness === "contested" };
}

Webhook

除了轮询,正式密钥还可以在会话进入最终状态时收到 POST 通知。Webhook 在发放正式密钥时一并配置(自助测试密钥不提供)。

事件
session.approved(含 idToken)· session.rejected · session.denied
grant.revoked:用户在 omni 中移除了你的应用(含 sub)。请按你的数据政策处理此人的数据。
POST https://your-app.example/xodian/webhook
Content-Type: application/json
User-Agent: xodian-webhook/1
X-Omni-Signature: t=1791251090,v1=5f1c…e9

{"event":"session.approved","sessionId":"…","status":"approved",
 "expiresAt":1791251375,"idToken":"eyJhbGciOiJFUzI1NiIs…"}

# when the person removes your app in omni:
{"event":"grant.revoked","sub":"oid_9f3c1a8be41d02c7aa50e07b"}

验证签名

请求头格式为 t=<Unix 秒>,v1=<hex>,其中 v1 = HMAC-SHA256(Webhook 密钥, t + "." + 原始请求体)。请拒绝过旧的时间戳以防重放。

import crypto from "node:crypto";

// Use the RAW request body, exactly as received.
function verifyXodian(rawBody, header, secret /* whsec_… */) {
  const parts = Object.fromEntries(header.split(",").map(p => p.split("=")));
  const expected = crypto.createHmac("sha256", secret)
    .update(`${parts.t}.${rawBody}`).digest("hex");
  const ok = parts.v1 && crypto.timingSafeEqual(
    Buffer.from(expected), Buffer.from(parts.v1.padEnd(expected.length)));
  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
  return ok && fresh;
}

投递

请在 8 秒内返回任意 2xx。否则我们会逐步拉长间隔重试,最多共 4 次。通知可能重复,请按 sessionId 幂等处理,并照常验证 idToken。

沙盒

测试密钥(rpt_…)免费且可自助创建。与生产环境一致,但有以下不同:

  • 会话通过组件中的模拟面板或 API 的模拟器来响应
  • 令牌带有 "test": true,sub 以 oid_test_ 开头
  • 没有 Webhook,也没有链上凭证
  • 每个密钥每天 500 个会话;每个网络每天最多创建 5 个新密钥

模拟器

curl -X POST https://xodian.xode.net/v1/test/sessions/$SESSION_ID/simulate \
  -H "Content-Type: application/json" \
  -d '{"outcome":"approve","level":2,"persona":"alice","shareWallet":true}'

# outcome:    approve | deny | reject | expire
# level:      1 | 2                      (approve)
# persona:    any name; same persona → same sub
# uniqueness: new | returning | contested (optional, forces the value)
# reason:     insufficient_level | device_unverified | no_device | integrity_stale (reject)

相同的 persona 总是对应相同的 sub,可用来测试"每人一次"和回访用户。以低于会话 minLevel 的等级批准时,会与生产环境一样判定为未通过。

未通过原因

未通过的会话带有 reason。请向用户显示中性提示,详细原因由 omni 私下告知本人。

reason含义用户可以怎么做
insufficient_level已验证,但低于你的 minLevel在这部手机上继续使用 omni;等级 2 随时间达成
device_unverified手机或应用未通过 Play Integrity / App Attest使用未修改的手机和商店版 omni
integrity_stale设备核验已过时打开 omni 重新核验后再试
no_device该钱包未绑定硬件密钥打开 omni 完成设备设置
device_mismatch、device_proof_failed响应的手机不是钱包绑定的手机用钱包所在的手机来响应
user_denied用户拒绝(状态 denied)无需处理,可以重试

遇到未知原因请按一般未通过处理,今后可能会新增代码。

链上凭证

每一个正式环境的判定(批准或拒绝)都会写入 Sentinel 账本,并每小时锚定到 XODE 链上。令牌中的 receipt 就是该记录的句柄。你或审计方之后可以证明:Xodian 在那个时间点确实做出了这个判定,而且此后无人修改。

GET https://xodian.xode.net/sentinel/receipt/1791251090-a1b2c3d4
GET https://xodian.xode.net/sentinel/anchors
GET https://xodian.xode.net/sentinel/verify.py     # independent verifier, standard library only

账本是公开的,因此不包含个人信息:对象是哈希值,而不是 sub 或钱包地址。

安全检查清单

  • 客户端密钥只放在你的服务器上,不要放进网页、应用包或代码仓库
  • 自己验证每一个令牌:签名、iss、aud、exp、nonce、一次性的 jti
  • "每人一次"的福利以 sub 为准,而不是钱包或邮箱
  • 上线前决定如何处理 contested(接受、审核或拒绝)
  • 涉及资金或投票的场景使用等级 2
  • 生产环境拒绝 test 令牌
  • 只保存需要的数据:通常 sub 和日期就足够了

限额与密钥

测试密钥 rpt_正式密钥 rp_
获取方式自助创建联系我们;面向基于 XODE 开发的合作方提前开放
每日会话数500按合作方约定(默认 10,000)
等级1–2不超过约定的等级
wallet 权限支持开通后支持
Webhook、凭证、认证合作方标识—支持

会话有效期 5 分钟,令牌 10 分钟。正式密钥可应要求轮换;新密钥签发的那一刻旧密钥即失效,请立即部署新密钥。

给你的用户

你可以把本节链接发给用户。用户看到的流程:

  1. 安装 omni

    在 Google Play 和 App Store 免费下载。omni 是在手机上运行轻节点的 XODE 应用。

  2. 扫码或点击

    在电脑上,用 omni 应用扫描二维码;在手机上,点击"在 omni 中继续"。

  3. 查看共享内容后确认

    omni 会显示是哪个应用在请求、以及它将收到什么。确认后会用钱包密钥和手机的硬件密钥签名。

  4. 完成

    应用只会知道你是真实用户,以及一个只有它能看到的私密 ID。你的姓名、手机号、余额和活动记录永远不会被共享。

管理已连接的应用

在 omni 的设置 → Xodian中,可以查看验证过的应用和当前等级,也可以移除应用。移除后应用会收到通知,但是否删除其已持有的数据由该应用决定,如有需要请直接联系它。

"我没有通过"

omni 会显示原因和解决方法。通常是:在未修改的手机上使用商店版 omni;打开 omni 让它重新核验设备;或者对于要求等级 2 的应用,在同一部手机上继续使用 omni 约两周。

常见问题

这是 KYC 吗?

不是。Xodian 只证明这是使用正版手机的一个真实的人,从不知道他是谁。许多团队把它放在 KYC 之前,只为通过的人支付完整核验的费用。

一个人还能获得多个 ID 吗?

每部正版手机一个。等级 2 要求每部手机持续使用数周,成本很高。Xodian 消除的是廉价手段:一台设备上的大量钱包、模拟器和被修改的应用。

换手机会怎样?

钱包迁移时,你的应用中的 sub 也会保持不变。钱包迁移到已被他人使用过的手机时,会返回 contested。

支持 iPhone 和 Android 吗?

支持。Android 使用 Google Play Integrity,iOS 使用 Apple App Attest。令牌中不会显示是哪个平台。

没有后端可以用吗?

不可以。令牌只会发给持有密钥的服务器,这正是结果可信的原因。

我是通过 app.xode.net/id 接入的,需要修改吗?

旧路径仍然可用。新的接入请使用 https://xodian.xode.net,验证令牌时应期望 iss = https://xodian.xode.net。

更新日志

  • 2026-10-06 迁移到 xodian.xode.net,启用短路径(/v1/…、/sdk.js、/s/<id>);签发方变更为 https://xodian.xode.net。扫码后的引导页支持 4 种语言。
  • 2026-10-05 v1:会话、等级 1–2、应用专属 ID、唯一性、组件、沙盒与模拟器、Webhook、链上凭证。

有问题或需要正式密钥?

最后更新 2026-10-07 · API v1
通过 Telegram 联系我们