Relay Endpoints
Relay purpose and trust model
Relay endpoints submit transactions on behalf of clients.
Use relay when:
- payer signs authorization but does not broadcast transactions
- your backend handles gas strategy and retries
Shared request envelope
What this does: defines fields common to all relay payment schemes.
{
network: 'bnbTestnet',
scheme: 'permit2' | 'eip2612' | 'eip3009' | 'push_signed',
intent: RelayIntent,
witness: RelayWitness,
witnessSignature: '0x...',
reference: 'order-1001',
session?: { sessionId, agent? },
sessionAuth?: { sessionId, intentHash, schemeId, spendNonce, expiresAt, epoch },
sessionAuthSignature?: '0x...'
}
Expected result: relay can build scheme-correct router calldata.
Shared setup for examples
What this does: prepares reusable relay input values.
import {
createApiClient,
createFlexIntent,
deriveEip3009Nonce,
hashPaymentIntent,
} from '@pepay/x402flex';
import { ethers } from 'ethers';
const ROUTER_ADDRESS = '0xf14f56A54E0540768b7bC9877BDa7a3FB9e66E91';
const TOKEN_ADDRESS = '0x55d398326f99059fF775485246999027B3197955';
const REFERENCE_ID = 'order-1001';
const api = createApiClient({
baseUrl: 'https://api.bnbpay.org',
apiKey: process.env.BNBPAY_RELAY_KEY,
});
const bundle = createFlexIntent({
merchant: '0x1111111111111111111111111111111111111111',
token: TOKEN_ADDRESS,
amount: ethers.parseUnits('25', 18),
chainId: 97,
referenceId: REFERENCE_ID,
scheme: 'exact:evm:permit2',
});
const intent = {
...bundle.intent,
amount: bundle.intent.amount.toString(),
};
const witness = bundle.witness;
const witnessSignature = '0xWITNESS_SIGNATURE';
const eip3009AuthNonce = deriveEip3009Nonce({
intentHash: hashPaymentIntent(bundle.intent),
router: ROUTER_ADDRESS,
chainId: 97,
});
Expected result: all examples below can be executed by replacing signatures and keys.
Permit2 payload
What this does: sends permit and transfer details for payWithPermit2.
await api.relay.payment({
network: 'bnbTestnet',
scheme: 'permit2',
intent,
witness,
witnessSignature,
permit2: {
permit: {
permitted: {
token: TOKEN_ADDRESS,
amount: intent.amount,
},
nonce: 1,
deadline: Math.floor(Date.now() / 1000) + 3600,
},
transferDetails: {
to: ROUTER_ADDRESS,
requestedAmount: intent.amount,
},
signature: '0xPERMIT2_SIGNATURE',
},
reference: REFERENCE_ID,
});
EIP-2612 payload
What this does: sends permit tuple for payWithEIP2612.
await api.relay.payment({
network: 'bnbTestnet',
scheme: 'eip2612',
intent,
witness,
witnessSignature,
eip2612: {
deadline: Math.floor(Date.now() / 1000) + 3600,
v: 27,
r: '0x' + '11'.repeat(32),
s: '0x' + '22'.repeat(32),
},
reference: REFERENCE_ID,
});
EIP-3009 payload
What this does: sends ReceiveWithAuthorization fields for payWithEIP3009.
await api.relay.payment({
network: 'bnbTestnet',
scheme: 'eip3009',
intent,
witness,
witnessSignature,
eip3009: {
validAfter: 0,
validBefore: Math.floor(Date.now() / 1000) + 3600,
authNonce: eip3009AuthNonce,
v: 27,
r: '0x' + '33'.repeat(32),
s: '0x' + '44'.repeat(32),
},
reference: REFERENCE_ID,
});
Important:
authNoncemust be intent-derived (intent hash + router + chain ID).
Session relay payloads
Use these endpoints for session lifecycle:
POST /relay/session/openPOST /relay/session/open-claimablePOST /relay/session/claimPOST /relay/session/revoke
For session spends through /relay/payment, include session, signed sessionAuth, and sessionAuthSignature.
Error codes and operator response
400validation failure:- malformed payload or missing scheme fields
401/403auth failure:- invalid/missing API key
422semantic failure:- expired auth, session mismatch, nonce mismatch
5xxtransient failure:- retry with status check and idempotent operation key