SDK & API
SDK Installation
The @recurcite/sdk is a server-first Node.js package for sending evidence events to RecurCite. It handles authentication, payload validation, HMAC signing, and batch operations.
Installation
npm install @recurcite/sdkOr with your preferred package manager:
yarn add @recurcite/sdk
# or
pnpm add @recurcite/sdkInitialization
Import and initialize the SDK with your API key. The client is stateless — you can create it once and reuse across your application.
import { init } from "@recurcite/sdk";
export const recurcite = init({
apiKey: process.env.RECURCITE_API_KEY!,
// Optional: HMAC signing for tamper-proof events
signingSecret: process.env.RECURCITE_SIGNING_SECRET,
});Server-side only
Never expose your API key or signing secret in client-side code. The SDK is designed for Node.js server environments only.
Sending events
track()
Send a single evidence event. Every event must include a type, payload, and ideally stripe_refs to link it to a Stripe customer.
await recurcite.track({
type: "terms.accepted",
payload: {
version: "2.0",
accepted_at: new Date().toISOString(),
},
stripe_refs: {
stripe_customer_id: "cus_abc123",
},
});batchTrack()
Send multiple events in a single request. Useful for bulk imports or high-throughput scenarios.
await recurcite.batchTrack([
{
type: "product.used",
payload: {
feature_key: "api_calls",
count: 150,
occurred_at: new Date().toISOString(),
},
stripe_refs: { stripe_customer_id: "cus_abc123" },
},
{
type: "user.login",
payload: {
occurred_at: new Date().toISOString(),
ip: "203.0.113.42",
},
stripe_refs: { stripe_customer_id: "cus_abc123" },
},
]);Idempotency
By default, the SDK generates a unique event_id for each event. To ensure retries don't create duplicates, provide your own deterministic ID:
import { generateEventId } from "@recurcite/sdk";
await recurcite.track({
event_id: generateEventId("terms.accepted", userId),
type: "terms.accepted",
payload: {
version: "2.0",
accepted_at: new Date().toISOString(),
},
});Tip
Use generateEventId(type, uniqueKey) to create deterministic IDs based on the event type and a unique identifier like the user ID or transaction ID.
HMAC signing
When you provide a signingSecret during initialization, the SDK automatically signs every request with an X-Recurcite-Signature header. The server verifies the signature and stores the verification status for audit and export flows.
const recurcite = init({
apiKey: process.env.RECURCITE_API_KEY!,
signingSecret: process.env.RECURCITE_SIGNING_SECRET,
});
// All subsequent track() calls are automatically signedError handling
The SDK never throws on network or server errors — it returns a structured result object. This ensures evidence tracking never breaks your application flow:
const result = await recurcite.track({
type: "terms.accepted",
payload: { version: "2.0", accepted_at: new Date().toISOString() },
stripe_refs: { stripe_customer_id: "cus_abc123" },
});
if (result.status === "error") {
console.error("RecurCite error:", result.error);
}
// result.status: "accepted" | "deduplicated" | "error"
// result.event_id: the generated or provided event ID
// result.error?: human-readable error message (only on error)| Status | Meaning | Action |
|---|---|---|
400 | Invalid payload — missing required fields or unknown event type | Fix the payload and retry |
401 | Invalid or missing API key | Check your RECURCITE_API_KEY |
409 | Duplicate event_id (idempotency conflict) | Event already recorded — safe to ignore |
429 | Rate limited | Back off and retry with exponential delay |
500 | Server error | Retry with backoff; contact support if persistent |
TypeScript types
The SDK is fully typed. Key types you'll work with:
import type {
TrackEvent,
StripeRefs,
EventType,
} from "@recurcite/sdk";
// EventType: "terms.accepted" | "user.login" | "product.used" | "transaction.completed" | ...
// StripeRefs: { stripe_customer_id?, stripe_subscription_id?, ... }
// TrackEvent: { type, payload, stripe_refs?, event_id? }CE3 integration (Visa Compelling Evidence 3.0)
To enable CE3 eligibility, send transaction.completed events with session signals (IP address, email, device fingerprint) after each successful charge. Use email_sha256 for email matching. Raw emails are rejected at ingestion.
await recurcite.track({
type: "transaction.completed",
payload: {
charge_id: "ch_abc123",
amount: 4999,
currency: "usd",
ip: req.ip,
email_sha256: createHash("sha256").update(user.email.toLowerCase().trim()).digest("hex"),
device_fingerprint: req.headers["x-device-id"],
product_description: "Pro Plan — Monthly",
merchandise_or_services: "services",
occurred_at: new Date().toISOString(),
},
stripe_refs: {
stripe_customer_id: user.stripeCustomerId,
},
});Tip
Send transaction.completed on every successful charge. CE3 requires at least 2 prior undisputed transactions within 120–365 days with matching IP or email signals.
Integration examples
Express middleware
import { recurcite } from "../lib/recurcite";
// Track logins on authentication
app.post("/auth/login", async (req, res) => {
const user = await authenticate(req.body);
await recurcite.track({
type: "user.login",
payload: {
occurred_at: new Date().toISOString(),
ip: req.ip,
user_agent: req.headers["user-agent"],
},
stripe_refs: {
stripe_customer_id: user.stripeCustomerId,
},
});
res.json({ token: generateToken(user) });
});Next.js API route
import { recurcite } from "@/lib/recurcite";
export async function POST(req: Request) {
const { userId, stripeCustomerId, termsVersion } = await req.json();
await recurcite.track({
type: "terms.accepted",
payload: {
version: termsVersion,
accepted_at: new Date().toISOString(),
},
stripe_refs: {
stripe_customer_id: stripeCustomerId,
},
});
return Response.json({ success: true });
}Get your API key
- Go to Dashboard → Developers
- Click Create key
- Copy the key (shown only once)
- Add to your environment:
RECURCITE_API_KEY=rc_live_…
Next steps
- Event Reference — all event types, required fields, and JSON examples
- Security & Data — HMAC signing, encryption, and privacy details