@ edge- markets/ 连接- 节点
服务器SDK用于EDGE Connect sorders交换和API调用.
特征
- 🔐 安全证券交换 - 与PKCE交换代号
- 🔄 托肯刷新 - 自动刷新令牌处理
- 📡 完整的 API 客户端 - 用户、余额、通过
forUser()模式 - 🛡️ 输入错误 - 每种情况的具体错误类别
- 📝 先输入脚本 - 完整的类型定义
- 🪝 Webhook 签名验证 - 常时HMAC-SHA256,有回放保护
- ✅ Webhook 解析+验证 - 对生体进行核查,对畸形事件有效载荷关闭故障
- 🔁 Webhook 调节 - 以光标为基础的同步,用于捕获错过的事件
- 📥 Webhook 收件箱管弦乐 - 与合作伙伴拥有的存储器进行持久的接收/处理/重放控制流程
- 🔐 托肯金库 - 版本的AES-256-GCM 密码加密,并有密钥旋转
- 🔄 用户会话 - 有刷新、加密和单飞行刷新保护的托肯存储回调
- ⚙️ 信封配置帮助器 - 从标准的EDGE env vars 构建 SDK 配置
这些帮助是框架中立的: SDK 拥有可重复的连接力学 例如令牌刷新、 webhook 验证、重放控制流程以及传输 意图检查,而伙伴应用程序则保留对持久性的所有权,钱包 变异、管理授权和商业规则.
安装
npm install @edge-markets/connect-node
# or
pnpm add @edge-markets/connect-node
# or
yarn add @edge-markets/connect-node
快速启动
import { createEdgeConnectServerFromEnv } from '@edge-markets/connect-node'
// Create instance once (reuse for all requests). Reads EDGE_CLIENT_ID,
// EDGE_CLIENT_SECRET, EDGE_ENVIRONMENT, optional mTLS and partner sync env vars.
const { server: edge } = createEdgeConnectServerFromEnv()
// Exchange code from EdgeLink for tokens
const tokens = await edge.exchangeCode(code, codeVerifier)
// Create a user-scoped client and make API calls
const client = edge.forUser(tokens.accessToken)
const user = await client.getUser()
const balance = await client.getBalance()
• 警卫
这个SDK需要你的客户秘密 只用在后端服务器上!
永远不要向浏览器或客户端代码透露您的客户端秘密.
配置
从环境变量
大多数伙伴后端应该使用 createEdgeConnectServerFromEnv() 如此的微调
PEM 处理、合作伙伴同步证书、 URL 覆盖和超时解析
在所有整合中保持一致.
import { createEdgeConnectServerFromEnv } from '@edge-markets/connect-node'
const { server: edge, capabilities, warnings } = createEdgeConnectServerFromEnv({
requireMtls: process.env.NODE_ENV === 'production',
})
warnings.forEach((warning) => logger.warn(warning))
if (!capabilities.webhookSyncConfigured) {
logger.warn('EDGE webhook sync is not configured; set EDGE_PARTNER_CLIENT_ID and EDGE_PARTNER_CLIENT_SECRET')
}
支持的环境变量 :
| 变量 | 说明 |
|---|---|
EDGE_CLIENT_ID | 面向用户的 OAuth 客户端ID |
EDGE_CLIENT_SECRET | OAuth 客户端秘密 |
EDGE_ENVIRONMENT | production 或者说 sandbox |
EDGE_API_BASE_URL | 可选的连接 API 覆盖 |
EDGE_OAUTH_BASE_URL | 可选 OAuth/ token URL 覆盖 |
EDGE_PARTNER_API_BASE_URL | 可选伙伴仪表板 API 覆盖 |
EDGE_PARTNER_CLIENT_ID | 伙伴机对机客户端 ID 用于 Webhook 同步 |
EDGE_PARTNER_CLIENT_SECRET | 用于Webhook同步的伙伴机对机客户端秘密 |
EDGE_MTLS_CERT 编号 : EDGE_MTLS_CERT_PATH | 客户端证书 PEM 或文件路径 |
EDGE_MTLS_KEY 编号 : EDGE_MTLS_KEY_PATH | 客户端私有密钥 PEM 或文件路径 |
EDGE_SERVER_CA_PEM 编号 : EDGE_SERVER_CA_PEM_PATH | 可选的额外服务器信任根 PEM 或文件路径 |
EDGE_MTLS_CA 编号 : EDGE_MTLS_CA_PATH | 可选的额外服务器信任根 PEM 或文件路径的后继别名 |
EDGE_REQUIRE_MTLS | 设定为 true 无法启动, 除非客户端证书和密钥已配置 |
EDGE_TIMEOUT_MS | 可选请求超时( 毫秒) |
使用带逃出新行的内置 PEMs(\n)或文件路径。 如果两者都安排好了,
内入值获胜,帮助者返回警告.
手动配置
interface EdgeConnectServerConfig {
clientId: string // Your user-facing OAuth client ID
clientSecret: string // Your user-facing OAuth client secret (keep secret!)
environment: EdgeEnvironment // 'production' | 'sandbox'
// Optional
apiBaseUrl?: string // Custom API URL (dev only)
partnerApiBaseUrl?: string // Custom partner dashboard API URL (dev only)
oauthBaseUrl?: string // Custom OAuth URL (dev only)
timeout?: number // Request timeout (default: 30000ms)
retry?: RetryConfig // Retry configuration
onRequest?: (info) => void // Hook called before each request
onResponse?: (info) => void // Hook called after each response
// Optional: Partner-level credentials for non-user-scoped endpoints.
// Required ONLY when calling syncWebhookEvents — the rest of the SDK
// works without them. These are a DIFFERENT OAuth client than clientId
// (machine-to-machine, not user-facing).
partnerClientId?: string
partnerClientSecret?: string
// By default the SDK derives partnerApiBaseUrl from apiBaseUrl by replacing
// /connect/v1 with /v1. For example:
// https://sandbox.connect.staging.edgeboost.io/connect/v1
// -> https://sandbox.connect.staging.edgeboost.io/v1
// Optional: Mutual TLS client authentication
mtls?: {
enabled: boolean
cert: string // Your client certificate PEM
key: string // Your client private key PEM
ca?: string | string[] // Additional server trust roots, if EDGE provides them
}
}
信件级别加密 (MLE)
教育、教育与教育部尚未提供。 边缘连接 API 不接受或返回加密的有效载荷,所以通过 mle: { enabled: true } 掷出 EdgeFeatureUnavailableError。连接API流量由mTLS保护.
互通 TLS (mTLS)
使用 mtls 当 EDGE 向您的伙伴后端发布了客户端证书和密钥时:
const edge = new EdgeConnectServer({
clientId: process.env.EDGE_CLIENT_ID!,
clientSecret: process.env.EDGE_CLIENT_SECRET!,
environment: 'sandbox',
mtls: {
enabled: true,
cert: process.env.EDGE_MTLS_CERT!,
key: process.env.EDGE_MTLS_KEY!,
},
})
cert 财务报告和审定财务报表 key 认证您的后端到 EDGE 连接 。 他们是客户
认证材料必须留在您的服务器上 .
ca 是可选服务器信任材料。 公开EDGE 连接网关等
https://connect.edgeboost.io 财务报告和审定财务报表 https://sandbox.connect.staging.edgeboost.io 使用情况
公开证书,所以大多数合伙人应该省略 ca如果 EDGE 提供
非公用端点的私用服务器 CA, 通过为 ca; SDK 附着
到节点默认的公共信任根 而不是替换它们.
不禁用 TLS 校验, 不设置 NODE_TLS_REJECT_UNAUTHORIZED=0。 。 。 .
Next.js 和 Vercel 路由处理器
@edge-markets/connect-node 是一个节点.js服务器 SDK. 在Next.js路处理器,
强制导入或构建 SDK 之前的节点运行时间 :
export const runtime = 'nodejs'
import { createEdgeConnectServerFromEnv } from '@edge-markets/connect-node'
const { server: edge } = createEdgeConnectServerFromEnv({
requireMtls: true,
})
不要从客户端组件、浏览器代码、中间软件导入此软件包, 或 边缘运行时处理器。 它只读取服务器机密,并使用节点 TLS API 用于 (原始内容存档于2012-10-12). mTLS.
如果您被绑入更旧的 SDK 版本, 您的 Next. js/ Turbopack 将建立日志
显示 dynamic usage of require is not supported,将软件包外部化为
应用级缓解直到您可以升级 :
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
serverExternalPackages: ['@edge-markets/connect-node', 'undici'],
}
export default nextConfig
托肯交换
EdgeLink 完成后, 将代码交换为令牌 :
// In your /api/edge/exchange endpoint
export async function POST(req: Request) {
const { code, codeVerifier } = await req.json()
try {
const tokens = await edge.exchangeCode(code, codeVerifier)
// Store tokens securely (encrypted in database)
await db.edgeConnections.upsert({
userId: req.user.id,
accessToken: encrypt(tokens.accessToken),
refreshToken: encrypt(tokens.refreshToken),
expiresAt: new Date(tokens.expiresAt),
})
return Response.json({ success: true })
} catch (error) {
if (error instanceof EdgeTokenExchangeError) {
// Code expired or already used
return Response.json({ error: 'Please try again' }, { status: 400 })
}
throw error
}
}
加密存储的令牌
在休息时存储访问和刷新加密的令牌 . EdgeTokenVault 给
* 与一个稳定的AES-256-GCM信封合作,提供关键标识和关键旋转支持;
您仍然选择数据库表格和用户映射.
import { createEdgeTokenVault } from '@edge-markets/connect-node'
const tokenVault = createEdgeTokenVault({
currentKey: {
id: process.env.EDGE_TOKEN_ENCRYPTION_KEY_ID ?? '2026-06',
key: process.env.EDGE_TOKEN_ENCRYPTION_KEY!, // 32 bytes as base64, base64url, or hex
},
previousKeys: [
// { id: '2026-05', key: process.env.EDGE_TOKEN_ENCRYPTION_KEY_2026_05! },
],
})
const tokens = await edge.exchangeCode(code, codeVerifier)
await db.edgeConnections.upsert({
userId,
encryptedTokens: tokenVault.encryptTokens(tokens),
})
const connection = await db.edgeConnections.get(userId)
const decrypted = tokenVault.decryptTokens(connection.encryptedTokens)
const client = edge.forUser(decrypted.accessToken)
托肯刷新和用户会话
使用 EdgeUserSession 当你存储加密的代号,并希望 SDK
处理解密-新鲜-拯救-客户生命周期。 您仍然拥有数据库表
和用户映射.
const session = edge.createUserSession({
subjectId: userId,
tokenVault,
tokenStore: {
load: async (subjectId) => {
const row = await db.edgeConnections.get(subjectId)
return row?.encryptedTokens
},
save: async (subjectId, encryptedTokens, { tokens }) => {
await db.edgeConnections.update(subjectId, {
encryptedTokens,
expiresAt: new Date(tokens.expiresAt),
})
},
},
refreshSkewMs: 60_000,
})
const balance = await session.withClient((client) => client.getBalance())
EdgeUserSession 刷新已近过期的符号, 当
符号端点不旋转, 保存已刷新的符号信封
在返回客户端之前,单飞行同时刷新同一客户端
进程。 在横向规模的服务中,增加每个链接的数据库租赁和
乐观的符号版本 。 坚持完整返回的符号
设置解剖,因为持久不透明的刷新证书在成功时旋转.
刷新失败抛出 EdgeRefreshError启用 actionRequired (单位:千美元)retry, (中文(简体) ),
partner_admin,或 relink和 retryable 连接状态决定;
不将每个符号端点失败都翻译为用户再连接提示 .
对于从分割符列迁移,请使用 createSessionTokenRecord, (中文(简体) ),
parseSessionTokenRecord, (中文(简体) ), normalizeSessionExpiresAt,以及
encryptLegacySessionTokens. 普通文字迁移要求明确
allowPlaintext: true 选项,所以生产请求路径不会默默接受
原始标志.
API 方法
所有用户扫描的 API 方法都使用 EdgeUserClient,通过 edge.forUser(accessToken)数字 :
const client = edge.forUser(accessToken)
用户余额( B)
const user = await client.getUser()
// Returns: { id, email, firstName, lastName, address?, city?, state?, zip?, phone?, dateOfBirth?, verified, createdAt }
const balance = await client.getBalance()
// Returns: { userId, currentBalance, availableBalance, currency, asOf }
转账
// 1. Initiate the transfer — returns a pending transfer that requires verification.
const transfer = await client.initiateTransfer({
type: 'debit', // 'debit' = pull from user, 'credit' = push to user
amount: '100.00',
idempotencyKey: `txn_${userId}_${Date.now()}`,
})
// Returns: { transferId, status: 'pending_verification', otpMethod }
// 2. Create an EDGE-hosted verification session. The returned `verificationUrl`
// is a single-use, short-lived URL you embed in an iframe or popup on your
// frontend. The user enters their OTP inside the EDGE-hosted UI — OTP secrets
// never touch your infrastructure.
const session = await client.createVerificationSession(transfer.transferId, {
origin: 'https://your-site.example.com',
})
// Returns: { sessionId, verificationUrl, expiresAt }
// 3. Listen for the postMessage event from the iframe (or poll
// getVerificationSessionStatus) to learn when verification completes.
// A `transfer.completed` webhook will be delivered to your configured
// webhook URL once the transfer settles. For browser-crash recovery,
// your backend should also reconcile against
// GET /v1/partner/webhooks/events/sync using a dashboard
// client_credentials token from POST /connect/oauth/token.
// Get transfer status
const status = await client.getTransfer(transfer.transferId)
// List transfers
const { transfers, total } = await client.listTransfers({
status: 'completed',
limit: 10,
offset: 0,
})
同意
// Revoke consent (disconnect user)
await client.revokeConsent()
// Clean up stored tokens
await db.edgeConnections.delete(userId)
网络呼号
EDGE Connect在传输完成后将webhook事件发送给您的服务器, 失败、过期或用户取消同意。 SDK提供两个帮助: 签名验证 用于 HTTP 直播频道和 a 调节同步方法 以克龙驱动的安全网.
解析和校验webhook 交付
每个网友都带着 X-Edge-Signature 页眉在 t=...,v1=...
格式 - HMAC-SHA256 超过 ${timestamp}.${rawBody} 键入了您的网页
暗号 使用 parseAndVerifyWebhook 核查签名,分析JSON
运行时间 - 将输入的事件信封逐一验证 。 帮手拒绝
默认超过5分钟的事件,以防止再播放攻击.
⚠️ 这个
body参数必须为原始请求字节,没有解析 对象,您重新字符串.JSON.stringify(req.body)重排序密钥 并且无声无息地打破了没有诊断的核查. 总是抓住 原始身体通过你框架的生体中间软件.
import { parseAndVerifyWebhook } from '@edge-markets/connect-node'
import express from 'express'
const app = express()
app.post(
'/webhooks/edge',
// 👇 Critical: this gives you req.body as a Buffer, not a parsed object
express.raw({ type: 'application/json' }),
(req, res) => {
const { event, eventId, signatureTimestamp } = parseAndVerifyWebhook({
rawBody: req.body,
signatureHeader: req.headers['x-edge-signature'],
secret: process.env.EDGE_WEBHOOK_SECRET!,
})
// Store eventId + signatureTimestamp in your durable inbox if you use one.
// Process asynchronously and return 200 immediately — see "Best Practices"
void processEdgeEvent(event)
res.status(200).send('ok')
},
)
网点JS
// main.ts
const app = await NestFactory.create(AppModule, { rawBody: true })
// edge-webhook.controller.ts
import { Controller, Post, Req, HttpCode, UnauthorizedException, type RawBodyRequest } from '@nestjs/common'
import type { Request } from 'express'
import { EdgeAuthenticationError, parseAndVerifyWebhook } from '@edge-markets/connect-node'
@Controller('webhooks')
export class EdgeWebhookController {
@Post('edge')
@HttpCode(200)
handle(@Req() req: RawBodyRequest<Request>) {
try {
const { event } = parseAndVerifyWebhook({
rawBody: req.rawBody ?? Buffer.from(''),
signatureHeader: req.headers['x-edge-signature'],
secret: process.env.EDGE_WEBHOOK_SECRET!,
})
void processEdgeEvent(event)
} catch (error) {
if (error instanceof EdgeAuthenticationError) {
throw new UnauthorizedException('invalid signature')
}
throw error
}
}
}
verifyWebhookSignature 仍为高级集成出口,但
parseAndVerifyWebhook 是建议默认的,因为它没有关闭
未知的事件类型,错误的有效载荷, 和解析的身体错误.
类型安全事件处理 EdgeWebhookEvent
EdgeWebhookEvent 歧视的结合 event.type切换
缩进 event.data 自动——不需要铸造.
import type { EdgeWebhookEvent } from '@edge-markets/connect-node'
function processEdgeEvent(event: EdgeWebhookEvent) {
switch (event.type) {
case 'transfer.completed':
// event.data is { transferId, status: 'completed', type, amount }
creditWallet(event.data.transferId, event.data.amount)
break
case 'transfer.failed':
// event.data.reason is `string | undefined`
logFailure(event.data.transferId, event.data.reason ?? 'unknown')
break
case 'transfer.expired':
cancelPending(event.data.transferId)
break
case 'transfer.processing':
// @experimental — reserved for ledger dual-write hand-off
break
case 'consent.revoked':
deactivateLink(event.data.userId, event.data.clientId)
break
default: {
const _exhaustive: never = event
return _exhaustive
}
}
}
event.data.amount 故意为: string (例如:. '100.00'页:1
数字,以保持小数精确度——用小数认识来分析
图书馆在你那边.
校正错过的事件 createWebhookReconciler
即使是复试和死字排队,网络或伙伴断线也能
放弃 Webhook 事件。 运行 syncWebhookEvents 从头到尾(每~5分钟)
是典型的)以捕捉任何您的主要接收器错过的.
这种方法需要合作伙伴的证书(partnerClientId 编号 :
partnerClientSecret继续 EdgeConnectServer它使用 OAuth
client_credentials 赠款,在内部缓存伙伴代号
处理一个 401 ,方法是刷新一次符牌并重试.
import { createEdgeConnectServerFromEnv } from '@edge-markets/connect-node'
const { server: edge } = createEdgeConnectServerFromEnv()
const reconciler = edge.createWebhookReconciler({
cursorStore: {
load: () => db.cursors.get('edge_webhook_sync'),
save: (cursor) => db.cursors.set('edge_webhook_sync', cursor),
},
processEvent: processEdgeEvent, // same idempotent handler as live webhooks
limit: 100,
maxPages: 10,
})
async function reconcile() {
const result = await reconciler.run()
logger.info('EDGE webhook reconciliation complete', result)
}
setInterval(reconcile, 5 * 60 * 1000)
校正器只在光标后保存光标 processEvent(event) 成功并
跳过重叠运行在同一实例上。 您提供了光标存储和
事件处理器; SDK 拥有页码和光标推进.
如果你需要全权控制, edge.syncWebhookEvents() 现保留.
服务器执行 a 30天的回顾。如果光标大于 30
你们将会收到30天前的事件
状态良好,通过 getTransfer 如果你已经下线的时间更长.
最佳做法
- 先验证签名 - 拒绝在401号事件前签名错误的事件 从事任何工作.
- 立即返回200号 ——过程事件同步. EDGE 重试 在非-2xx和反应慢于10s.
- 发自内心 - 同一活动可以不止一次进行
(一一一一送来). 按键您的钱包信用/ 状态更新
event.id或者说event.data.transferId。 。 。 . - 运行调节作为后台 - 网络呼喊是快速路径,
syncWebhookEvents这是安全网。 他们有相同的处理器.
syncWebhookEvents 使用与用户扫描的 API 相同的 SDK mTLS 传输
呼叫、获取和缓存合伙人 client_credentials 内部标志,
与 SDK 重试策略重试瞬时同步失败, 并编辑符号
材质来自可观察钩.
持久网点收件箱 createWebhookInbox
对于生产集成,接受经过验证的网络钩入耐用存储器
返回之前 200,然后同步处理。 SDK可以拥有
当您的应用程序拥有数据库召回和钱包突变时, 状态机器 .
import {
createWebhookInbox,
parseWebhookHttpRequest,
} from '@edge-markets/connect-node'
const inbox = createWebhookInbox({
store: {
insert: db.webhookInbox.insertIdempotently,
find: db.webhookInbox.find,
markProcessing: db.webhookInbox.markProcessing,
markProcessed: db.webhookInbox.markProcessed,
markFailed: db.webhookInbox.markFailed,
markPending: db.webhookInbox.markPending,
list: db.webhookInbox.list,
},
processEvent: async (event) => {
// Partner-owned business logic. Keep wallet mutation idempotent.
await processEdgeEvent(event)
},
})
app.post('/webhooks/edge', express.raw({ type: 'application/json' }), async (req, res) => {
const parsed = parseWebhookHttpRequest({
request: { headers: req.headers, rawBody: req.body },
secret: process.env.EDGE_WEBHOOK_SECRET!,
})
const accepted = await inbox.acceptRaw(
parsed,
Buffer.from(req.body).toString('utf8'),
String(req.headers['x-edge-signature'] ?? ''),
)
res.status(200).json({
received: true,
duplicate: accepted.duplicate,
status: accepted.status,
})
void inbox.process(accepted.eventId).catch((error) => {
logger.error('EDGE webhook processing failed', { eventId: accepted.eventId, error })
})
})
收件箱状态为 pending, (中文(简体) ), processing, (中文(简体) ), processed,以及 failed。 。 。 .
复制 event.id 如果有效载荷指纹,交货即为同位素
火柴。 比赛。 不同有效载荷的重复事件ID失败关闭 .
同步调节可以使用相同的收件箱路径 :
const reconciler = edge.createWebhookReconciler({
cursorStore,
inbox,
limit: 100,
})
调和器接受每个同步事件进入收件箱,处理并保存
光标仅在处理成功或事件已经处理之后.
使用 inbox.replay(eventId) 用于明确的管理员重放和
inbox.recoverStaleProcessing() 将崩溃 processing 行可回放.
转移安全助手
使用传输助手来避免小数和方向错误 :
import {
assertWebhookMatchesTransfer,
createTransferIdempotencyKey,
getConnectTransferDirection,
mapTransferStatusToPartnerState,
normalizeMoneyAmount,
validateTransferAmount,
} from '@edge-markets/connect-node'
const amount = validateTransferAmount(input.amount, { min: '1.00', max: '10000.00' }, { allowNumber: false })
const idempotencyKey = createTransferIdempotencyKey({
partnerUserId: user.id,
type: 'debit',
amount,
category: 'sportsbook',
externalId: bet.id,
})
// debit = EDGE user to partner; credit = partner to EDGE user
const direction = getConnectTransferDirection('debit')
assertWebhookMatchesTransfer(event, {
transferId: localTransfer.edgeTransferId,
type: 'debit',
amount: localTransfer.amount,
})
处理错误
import {
EdgeError,
EdgeAuthenticationError,
EdgeTokenExchangeError,
EdgeConsentRequiredError,
isEdgeError,
} from '@edge-markets/connect-node'
try {
const client = edge.forUser(accessToken)
const balance = await client.getBalance()
} catch (error) {
if (error instanceof EdgeAuthenticationError) {
// Token expired - try refresh or reconnect
return { error: 'session_expired' }
}
if (error instanceof EdgeConsentRequiredError) {
// User revoked consent - need to reconnect
return { error: 'reconnect_required' }
}
if (isEdgeError(error)) {
// Some other SDK error
console.error(`Edge Error [${error.code}]: ${error.message}`)
return { error: error.code }
}
// Unknown error
throw error
}
错误类型
| 错误 | 何时 | 怎么办 |
|---|---|---|
EdgeAuthenticationError | 键入无效/ 过期 | 刷新符号或重新连接 |
EdgeTokenExchangeError | 代码交换失败 | 请用户再次尝试 |
EdgeConsentRequiredError | 用户没有同意 | 打开边缘链接 |
EdgeInsufficientScopeError | 缺少所需范围 | 请求更多范围 |
EdgeNotFoundError | 未找到资源 | 检查身份 |
EdgeApiError | 其他 API 错误 | 检查错误. code |
EdgeNetworkError | 网络失败 | 重试请求 |
NestJS 示例
import { Injectable, Logger } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
import { EdgeConnectServer, EdgeConsentRequiredError } from '@edge-markets/connect-node'
@Injectable()
export class EdgeService {
private readonly edge: EdgeConnectServer
private readonly logger = new Logger(EdgeService.name)
constructor(private config: ConfigService) {
this.edge = new EdgeConnectServer({
clientId: this.config.getOrThrow('EDGE_CLIENT_ID'),
clientSecret: this.config.getOrThrow('EDGE_CLIENT_SECRET'),
environment: this.config.get('EDGE_ENVIRONMENT', 'sandbox'),
})
}
async exchangeCode(code: string, codeVerifier: string) {
return this.edge.exchangeCode(code, codeVerifier)
}
async getBalance(accessToken: string) {
try {
const client = this.edge.forUser(accessToken)
return await client.getBalance()
} catch (error) {
if (error instanceof EdgeConsentRequiredError) {
this.logger.warn('User consent required')
throw error
}
this.logger.error('Failed to get balance', error)
throw error
}
}
}
快捷示例
import express from 'express'
import { EdgeConnectServer, isEdgeError } from '@edge-markets/connect-node'
const edge = new EdgeConnectServer({
clientId: process.env.EDGE_CLIENT_ID!,
clientSecret: process.env.EDGE_CLIENT_SECRET!,
environment: 'sandbox',
})
const app = express()
app.use(express.json())
// Exchange code for tokens
app.post('/api/edge/exchange', async (req, res) => {
try {
const { code, codeVerifier } = req.body
const tokens = await edge.exchangeCode(code, codeVerifier)
// Store tokens for user...
res.json({ success: true })
} catch (error) {
if (isEdgeError(error)) {
res.status(400).json({ error: error.code, message: error.message })
} else {
res.status(500).json({ error: 'internal_error' })
}
}
})
// Get balance
app.get('/api/edge/balance', async (req, res) => {
try {
const accessToken = await getAccessTokenForUser(req.user.id)
const client = edge.forUser(accessToken)
const balance = await client.getBalance()
res.json(balance)
} catch (error) {
// Handle errors...
}
})
相关软件包
@edge-markets/connect- 核心类型和公用事业@edge-markets/connect-link- 用于弹出认证的浏览器 SDK
许可证
麻省理工学院
只读的 Webhooks v2 (2026-09-06) (中文(简体) )
有门的释放:这些出口船,下一次释放含有这一变化;
现有3.0.0装置尚未提供。 发布 - 请拥有版本.
parseAndVerifyWebhookV2 用5分钟的时间戳验证准确的生字节
窗口、显式单元格和活性签名密钥(或边框密钥环).
它区分了经认证的未来类型和无效的已知类型。 唯一一个
商务活动是 consent.revoked; 保留其实际 grantId 发件人
在更改链接前匹配单元格/客户端/用户/赠款。 忽略 test: true。 。 。 .
WebhookInboxV2 需要一个具有独特特性的应用程序拥有的可持久适配器
租赁和围栏。 2xx 之前提交接受; 运行 a
单独的重启安全工人. reconcileWebhooksV2 需要分配租赁
,将每个事件与光标一起插入一页。 从不
部分失败后提前. WebhookCursorExpiredError 所需赠款
从快照上取出水印.
EdgeConnectServer.syncWebhookEventsV2, (中文(简体) ), getWebhookGrantSnapshot 财务报告和审定财务报表
getWebhookCapabilities 使用具有 mTLS 的单独伙伴机证书.
partnerApiBaseUrl 一定是犬科细胞网关+ /v1 (SDK游戏机)
附录 /partner/webhooks细胞在中转 沙盒被粘住,
沙盒生产和生产。 没有用户端或平正文本传输回落 .
正常旋转在24小时重叠期间验证任何活性的v1签名. 过期
明示前一个接收键; 绝不记录原始签名、 证书或密钥.
离线 connect-webhook-inspect --cell sandbox-staging --signature-file /protected/signature.txt < /protected/body.json 命令从
EDGE_WEBHOOK_SECRET 并打印只验证安全元数据。 没有HTTP
请求。 使用仪表板进行真正的沙盒测试,尝试和确认再发.
见文件处网站的Webhooks、签名核查和回收指南和 亚历山大港的PostgreSQL接收器用于一个完整的应用程序适配器. 遗产 出口仍然可用;现在,遗留的调节工作停止于未处理的工作.