@edge-markets/connect-node
v1.11.2
Published
Server SDK for EDGE Connect token exchange and API calls
Readme
@edge-markets/connect-node
Server SDK for EDGE Connect token exchange and API calls.
Features
- 🔐 Secure token exchange - Exchange codes for tokens with PKCE
- 🔄 Token refresh - Automatic refresh token handling
- 📡 Full API client - User, balance, transfers via
forUser()pattern - 🛡️ Typed errors - Specific error classes for each scenario
- 📝 TypeScript first - Complete type definitions
- 🪝 Webhook signature verification - Constant-time HMAC-SHA256 with replay protection
- ✅ Webhook parsing + validation - Verify raw bodies and fail closed on malformed event payloads
- 🔁 Webhook reconciliation - Cursor-based sync for catching missed events
- 📥 Webhook inbox orchestration - Durable accept/process/replay control flow with partner-owned storage
- 🔐 Token vault - Versioned AES-256-GCM token encryption with key rotation
- 🔄 User sessions - Token-store callbacks with refresh, encryption, and single-flight refresh protection
- ⚙️ Env config helper - Build SDK config from standard EDGE env vars
These helpers are framework-neutral: the SDK owns repeatable Connect mechanics such as token refresh, webhook validation, replay control flow, and transfer intent checks, while partner applications keep ownership of persistence, wallet mutation, admin authorization, and business rules.
Installation
npm install @edge-markets/connect-node
# or
pnpm add @edge-markets/connect-node
# or
yarn add @edge-markets/connect-nodeQuick Start
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()⚠️ Security
This SDK requires your client secret. Use it ONLY on your backend server!
Never expose your client secret to browsers or client-side code.
Configuration
From environment variables
Most partner backends should use createEdgeConnectServerFromEnv() so mTLS
PEM handling, partner sync credentials, URL overrides, and timeout parsing are
consistent across integrations.
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')
}Supported environment variables:
| Variable | Description |
|----------|-------------|
| EDGE_CLIENT_ID | User-facing OAuth client ID |
| EDGE_CLIENT_SECRET | User-facing OAuth client secret |
| EDGE_ENVIRONMENT | production or sandbox |
| EDGE_API_BASE_URL | Optional Connect API override |
| EDGE_OAUTH_BASE_URL | Optional OAuth/token URL override |
| EDGE_PARTNER_API_BASE_URL | Optional partner dashboard API override |
| EDGE_PARTNER_CLIENT_ID | Partner machine-to-machine client ID for webhook sync |
| EDGE_PARTNER_CLIENT_SECRET | Partner machine-to-machine client secret for webhook sync |
| EDGE_MTLS_CERT / EDGE_MTLS_CERT_PATH | Client certificate PEM or file path |
| EDGE_MTLS_KEY / EDGE_MTLS_KEY_PATH | Client private key PEM or file path |
| EDGE_SERVER_CA_PEM / EDGE_SERVER_CA_PEM_PATH | Optional extra server trust root PEM or file path |
| EDGE_MTLS_CA / EDGE_MTLS_CA_PATH | Legacy alias for optional extra server trust root PEM or file path |
| EDGE_REQUIRE_MTLS | Set to true to fail startup unless client cert and key are configured |
| EDGE_TIMEOUT_MS | Optional request timeout in milliseconds |
Use inline PEMs with escaped newlines (\n) or file paths. If both are set,
the inline value wins and the helper returns a warning.
Manual configuration
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: Message Level Encryption (Connect endpoints only)
mle?: {
enabled: boolean
edgePublicKey: string // EDGE public encryption key (PEM)
edgeKeyId: string // EDGE key ID (kid) used for requests
partnerPrivateKey: string // Your private key (PEM) to decrypt responses
partnerKeyId: string // Your key ID (kid) expected in response headers
strictResponseEncryption?: boolean // default true
}
// 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
}
}Message Level Encryption (MLE)
const edge = new EdgeConnectServer({
clientId: process.env.EDGE_CLIENT_ID!,
clientSecret: process.env.EDGE_CLIENT_SECRET!,
environment: 'sandbox',
mle: {
enabled: true,
edgePublicKey: process.env.EDGE_MLE_EDGE_PUBLIC_KEY!,
edgeKeyId: process.env.EDGE_MLE_EDGE_KEY_ID!,
partnerPrivateKey: process.env.EDGE_MLE_PARTNER_PRIVATE_KEY!,
partnerKeyId: process.env.EDGE_MLE_PARTNER_KEY_ID!,
},
})When enabled, the SDK sends X-Edge-MLE: v1, encrypts request bodies, and decrypts encrypted Connect responses.
Mutual TLS (mTLS)
Use mtls when EDGE has issued your partner backend a client certificate and key:
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 and key authenticate your backend to EDGE Connect. They are client
authentication material and must stay on your server.
ca is optional server trust material. Public EDGE Connect gateways such as
https://connect.edgeboost.io and https://sandbox.connect.staging.edgeboost.io use
public certificates, so most partners should omit ca. If EDGE provides a
private server CA for a non-public endpoint, pass it as ca; the SDK appends it
to Node's default public trust roots instead of replacing them.
Do not disable TLS verification and do not set NODE_TLS_REJECT_UNAUTHORIZED=0.
Next.js and Vercel Route Handlers
@edge-markets/connect-node is a Node.js server SDK. In Next.js Route Handlers,
force the Node runtime before importing or constructing the SDK:
export const runtime = 'nodejs'
import { createEdgeConnectServerFromEnv } from '@edge-markets/connect-node'
const { server: edge } = createEdgeConnectServerFromEnv({
requireMtls: true,
})Do not import this package from Client Components, browser code, middleware, or Edge Runtime handlers. It reads server-only secrets and uses Node TLS APIs for mTLS.
If you are pinned to an older SDK version and your Next.js/Turbopack build logs
show dynamic usage of require is not supported, externalize the package as an
app-level mitigation until you can upgrade:
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
serverExternalPackages: ['@edge-markets/connect-node', 'undici'],
}
export default nextConfigToken Exchange
After EdgeLink completes, exchange the code for tokens:
// 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
}
}Encrypting stored tokens
Store access and refresh tokens encrypted at rest. EdgeTokenVault gives
partners a stable AES-256-GCM envelope with key IDs and key rotation support;
you still choose the database table and user mapping.
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)Token Refresh And User Sessions
Use EdgeUserSession when you store encrypted tokens and want the SDK to
handle decrypt-refresh-save-client lifecycle. You still own the database table
and user mapping.
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 refreshes near-expiry tokens, preserves refresh tokens when
the token endpoint does not rotate them, saves the refreshed token envelope
before returning a client, and single-flights concurrent refreshes on the same
process. In horizontally scaled services, keep your storage callbacks
idempotent or add DB-level optimistic locking.
For migrations from split token columns, use createSessionTokenRecord,
parseSessionTokenRecord, normalizeSessionExpiresAt, and
encryptLegacySessionTokens. Plaintext migration requires an explicit
allowPlaintext: true option so production request paths do not silently accept
raw tokens.
API Methods
All user-scoped API methods live on EdgeUserClient, created via edge.forUser(accessToken):
const client = edge.forUser(accessToken)User & Balance
const user = await client.getUser()
// Returns: { id, email, firstName, lastName, createdAt }
const balance = await client.getBalance()
// Returns: { userId, availableBalance, currency, asOf }Transfers
// 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,
})Consent
// Revoke consent (disconnect user)
await client.revokeConsent()
// Clean up stored tokens
await db.edgeConnections.delete(userId)Webhooks
EDGE Connect delivers webhook events to your server when transfers complete, fail, expire, or when a user revokes consent. The SDK provides two helpers: signature verification for the live HTTP delivery channel, and a reconciliation sync method for the cron-driven safety net.
Parse and verify webhook deliveries
Every webhook arrives with an X-Edge-Signature header in t=...,v1=...
format — HMAC-SHA256 over ${timestamp}.${rawBody} keyed by your webhook
secret. Use parseAndVerifyWebhook to verify the signature, parse JSON, and
runtime-validate the typed event envelope in one step. The helper rejects
events older than 5 minutes by default to prevent replay attacks.
⚠️ The
bodyargument MUST be the raw request bytes, not a parsed object that you re-stringified.JSON.stringify(req.body)reorders keys and silently breaks verification with no diagnostic. Always capture the raw body via your framework's raw-body middleware.
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')
},
)NestJS
// 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 remains exported for advanced integrations, but
parseAndVerifyWebhook is the recommended default because it fails closed on
unknown event types, malformed payloads, and parsed-body mistakes.
Type-safe event handling with EdgeWebhookEvent
EdgeWebhookEvent is a discriminated union over event.type. Switching
narrows event.data automatically — no casts required.
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 is intentionally a string (e.g. '100.00'), not a
number, to preserve decimal precision — parse it with a decimal-aware
library on your side.
Reconcile missed events with createWebhookReconciler
Even with retries and a dead-letter queue, network or partner outages can
drop webhook events. Run syncWebhookEvents from a cron (every ~5 minutes
is typical) to catch anything your primary receiver missed.
This method requires the partner-level credentials (partnerClientId /
partnerClientSecret) on EdgeConnectServer. It uses the OAuth
client_credentials grant, caches the partner token internally, and
handles a 401 by refreshing the token once and retrying.
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)The reconciler saves the cursor only after processEvent(event) succeeds and
skips overlapping runs on the same instance. You provide cursor storage and
the idempotent event handler; the SDK owns pagination and cursor advancement.
If you need full control, edge.syncWebhookEvents() remains available.
The server enforces a 30-day lookback. If your cursor is older than 30
days, you will receive events from 30 days ago — re-bootstrap from a known
good state via getTransfer if you have been offline for longer.
Best practices
- Verify signatures first — reject mis-signed events with 401 before doing any work.
- Return 200 immediately — process events asynchronously. EDGE retries on non-2xx and on responses slower than 10s.
- Be idempotent — the same event may be delivered more than once
(at-least-once delivery). Key your wallet credit / status update on
event.idorevent.data.transferId. - Run reconciliation as a backstop — webhooks are the fast path,
syncWebhookEventsis the safety net. They share the same handler.
syncWebhookEvents uses the same SDK mTLS transport as user-scoped API
calls, fetches and caches the partner client_credentials token internally,
retries transient sync failures with the SDK retry policy, and redacts token
material from observability hooks.
Durable webhook inbox with createWebhookInbox
For production integrations, accept a verified webhook into durable storage
before returning 200, then process it asynchronously. The SDK can own the
state machine while your app owns the database callbacks and wallet mutation.
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 })
})
})The inbox statuses are pending, processing, processed, and failed.
Duplicate event.id deliveries are idempotent if the payload fingerprint
matches. A duplicate event ID with different payload fails closed.
Sync reconciliation can use the same inbox path:
const reconciler = edge.createWebhookReconciler({
cursorStore,
inbox,
limit: 100,
})The reconciler accepts each synced event into the inbox, processes it, and saves
the cursor only after processing succeeds or the event is already processed.
Use inbox.replay(eventId) for explicit admin replays and
inbox.recoverStaleProcessing() to make crashed processing rows replayable.
Transfer safety helpers
Use transfer helpers to avoid decimal and direction mistakes:
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,
})Error Handling
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
}Error Types
| Error | When | What to do |
|-------|------|------------|
| EdgeAuthenticationError | Token invalid/expired | Refresh token or reconnect |
| EdgeTokenExchangeError | Code exchange failed | Ask user to try again |
| EdgeConsentRequiredError | User hasn't granted consent | Open EdgeLink |
| EdgeInsufficientScopeError | Missing required scopes | Request more scopes |
| EdgeNotFoundError | Resource not found | Check ID |
| EdgeApiError | Other API error | Check error.code |
| EdgeNetworkError | Network failure | Retry request |
NestJS Example
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
}
}
}Express Example
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...
}
})Related Packages
@edge-markets/connect- Core types and utilities@edge-markets/connect-link- Browser SDK for popup authentication
License
MIT
