Skip to content

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.

Terminal window
npm install @tallypay/core

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.


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

PropertyTypeRequiredDescription
onPaymentRequired(req: PaymentRequired, url: string) => Promise<string>YesCalled on 402. Must return a base64-encoded payment payload for the payment-signature header.
onLifecycleEvent(eventType: string, metadata?: Record<string, unknown>) => voidNoCalled at each lifecycle step (402_RECEIVED, PAYMENT_SIGNED, PAYMENT_SUBMITTED, PAYMENT_COMPLETE, PAYMENT_ERROR).

Flow:

  1. Calls fetchFn(input, init).
  2. If status is not 402, returns the response unchanged.
  3. Reads the payment-required header, base64-decodes and parses it.
  4. Calls onPaymentRequired with the parsed requirements.
  5. Retries the request with payment-signature set to the returned string.
  6. Returns the retry response.

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

PropertyTypeDefaultDescription
apiKeystringYour TallyPay API key. Included in every event.
collectorUrlstringhttps://collector.tallypay.devCollector endpoint. Override for local dev.
batchSizenumber10Max events before auto-flush.
flushIntervalMsnumber5000Max time before auto-flush.

EventEmitter interface

MethodDescription
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.


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;
}

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 failed

LifecycleEventBase

interface LifecycleEventBase {
traceId: string;
apiKey: string;
eventType: PaymentLifecycleEventType;
source: "client" | "server" | "proxy";
timestamp: number;
metadata?: Record<string, unknown>;
}