Core primitives
@tallypay/core is framework-agnostic. No runtime dependencies beyond platform APIs (fetch, crypto). Works in Node, Deno, Bun, Cloudflare Workers, and the browser.
npm install @tallypay/coregenerateTraceId()
Section titled “generateTraceId()”Returns a sortable, unique string ID for correlating events across client and server.
import { generateTraceId } from "@tallypay/core";
const traceId = generateTraceId();// e.g. "01HYX3K..."Used internally by @tallypay/server middleware. You only need this directly if you’re building custom instrumentation.
wrapFetch(fetchFn, config)
Section titled “wrapFetch(fetchFn, config)”Wraps the native fetch to intercept 402 responses, call your signer, and retry with a payment-signature header.
import { wrapFetch } from "@tallypay/core";
const payFetch = wrapFetch(fetch, { onPaymentRequired: async (paymentRequired, url) => { // paymentRequired: { x402Version, accepts: [{ scheme, network, payTo, ... }] } // Sign the payment, return base64-encoded payload string return btoa(JSON.stringify(signedPayload)); }, onLifecycleEvent: (eventType, metadata) => { // Optional tracing callback console.log(eventType, metadata); },});
const res = await payFetch("/api/premium");WrapFetchConfig
| Property | Type | Required | Description |
|---|---|---|---|
onPaymentRequired | (req: PaymentRequired, url: string) => Promise<string> | Yes | Called on 402. Must return a base64-encoded payment payload for the payment-signature header. |
onLifecycleEvent | (eventType: string, metadata?: Record<string, unknown>) => void | No | Called at each lifecycle step (402_RECEIVED, PAYMENT_SIGNED, PAYMENT_SUBMITTED, PAYMENT_COMPLETE, PAYMENT_ERROR). |
Flow:
- Calls
fetchFn(input, init). - If status is not 402, returns the response unchanged.
- Reads the
payment-requiredheader, base64-decodes and parses it. - Calls
onPaymentRequiredwith the parsed requirements. - Retries the request with
payment-signatureset to the returned string. - Returns the retry response.
createEventEmitter(config)
Section titled “createEventEmitter(config)”Creates a batching, fire-and-forget emitter for sending lifecycle events to the TallyPay collector.
import { createEventEmitter } from "@tallypay/core";
const emitter = createEventEmitter({ apiKey: "tp_live_...", collectorUrl: "http://127.0.0.1:8787", // defaults to https://collector.tallypay.dev batchSize: 10, // flush after N events (default: 10) flushIntervalMs: 5000, // or after N ms (default: 5000)});
emitter.emit({ traceId: "01HYX3K...", eventType: "402_ISSUED", source: "server", metadata: { endpoint: "/api/data", price: "10000" },});
// Force-flush (e.g. on process shutdown)await emitter.flush();EventEmitterConfig
| Property | Type | Default | Description |
|---|---|---|---|
apiKey | string | — | Your TallyPay API key. Included in every event. |
collectorUrl | string | https://collector.tallypay.dev | Collector endpoint. Override for local dev. |
batchSize | number | 10 | Max events before auto-flush. |
flushIntervalMs | number | 5000 | Max time before auto-flush. |
EventEmitter interface
| Method | Description |
|---|---|
emit(event) | Queues a lifecycle event. Non-blocking. |
flush() | Sends all queued events immediately. Returns a promise. |
All network errors are silently caught. Telemetry never blocks or breaks payment flows.
Protocol types
Section titled “Protocol types”TypeScript types mirroring common x402 shapes. Confirm field-level details against your facilitator’s current spec for production.
PaymentRequirements
interface PaymentRequirements { scheme: string; // e.g. "exact" network: string; // CAIP-2, e.g. "eip155:8453" asset: string; // e.g. "USDC" payTo: string; // merchant receive address maxAmountRequired: string; // base units (6 decimals for USDC) maxTimeoutSeconds?: number; description?: string; mimeType?: string; outputSchema?: Record<string, unknown>; extra?: Record<string, unknown>;}PaymentRequired — the full 402 body:
interface PaymentRequired { x402Version: number; accepts: PaymentRequirements[]; error?: string;}PaymentPayload — what the client sends back:
interface PaymentPayload { x402Version: number; scheme: string; network: string; payload: Record<string, unknown>;}PaymentResponse — server’s success response metadata:
interface PaymentResponse { success: boolean; transaction?: string; network?: string; payer?: string; errorReason?: string;}Lifecycle event types
Section titled “Lifecycle event types”type PaymentLifecycleEventType = | "402_ISSUED" // Server sent a 402 | "402_RECEIVED" // Client received the 402 | "PAYMENT_SIGNED" // Client signed the authorization | "PAYMENT_SUBMITTED" // Client retried with payment-signature | "VERIFY_REQUESTED" // Server called facilitator /verify | "VERIFY_RESULT" // Facilitator returned verify result | "SETTLE_REQUESTED" // Server called facilitator /settle | "SETTLE_RESULT" // Facilitator returned settle result | "PAYMENT_COMPLETE" // Full flow succeeded | "PAYMENT_ERROR"; // Any step failedLifecycleEventBase
interface LifecycleEventBase { traceId: string; apiKey: string; eventType: PaymentLifecycleEventType; source: "client" | "server" | "proxy"; timestamp: number; metadata?: Record<string, unknown>;}