npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

creem-datafast-sdk

v0.1.0

Published

Connect CREEM payments with DataFast revenue attribution. Automatically track payment conversions from traffic sources.

Readme

creem-datafast-sdk

Connect CREEM payments with DataFast revenue attribution. Automatically track which traffic sources drive your revenue.

What problem this solves

When using CREEM for payments and DataFast for analytics, you need to connect the two: attribute each payment to the visitor who made it. This package handles the full pipeline:

  1. Client-side: Read the datafast_visitor_id cookie set by the DataFast tracking script
  2. Checkout: Inject the visitor ID into CREEM checkout metadata
  3. Webhook: Verify CREEM webhook signatures, extract payment data, and send it to DataFast's payment tracking API

Without this, you'd write the same glue code in every project.

Features

  • Edge Runtime Support — Uses Web Crypto API (crypto.subtle) instead of node:crypto. Works on Vercel Edge Functions, Cloudflare Workers, and Node.js 18+.
  • Retry with Exponential Backoff — Automatic retries (3 attempts with jitter) for transient DataFast API failures. Configurable.
  • Dead Letter CallbackonDeadLetter fires when a webhook event cannot be forwarded after all retries. Log to DB, alert Slack, or queue for replay.
  • Refund Support — Handles refund.created events with negative amounts for accurate revenue attribution.
  • Idempotency with TTL — Built-in in-memory store with 7-day auto-expiry prevents memory leaks. Production-ready Upstash Redis adapter included.
  • Webhook Event Filtering — Process only the events you care about. Silently acknowledge the rest.
  • Dry-Run Mode — Run the full pipeline without sending to DataFast. Logs what would be sent.
  • Webhook Replay — Re-process failed webhooks manually, bypassing idempotency checks.
  • Transaction Hydration — Optionally fetch full order details from CREEM API for richer attribution data.
  • Checkout URL Builder — Build CREEM checkout URLs with attribution baked in. No server required.
  • Health Check — Verify CREEM config, webhook secret, and DataFast API reachability in one call.
  • Auto-Attribute Browser Links — Automatically append visitor IDs to CREEM checkout links on the page.
  • Batch Payment API — Backfill historical transactions into DataFast in bulk.
  • DataFast Query SDK — Look up payments by visitor ID for debugging attribution gaps.

Install

npm install creem-datafast-sdk creem

creem is a peer dependency — you need it installed alongside this package.

Configuration

import { createCreemDataFast, InMemoryIdempotencyStore } from 'creem-datafast-sdk';

const cd = createCreemDataFast({
  // Required
  creemApiKey: process.env.CREEM_API_KEY!,
  creemWebhookSecret: process.env.CREEM_WEBHOOK_SECRET!,
  datafastApiKey: process.env.DATAFAST_API_KEY!,

  // Optional
  creemServerIdx: 0,                              // 0 = production, 1 = sandbox
  datafastApiBaseUrl: 'https://datafa.st',         // default
  debug: false,                                    // enable debug logging
  requestTimeoutMs: 10_000,                        // DataFast API timeout
  requireVisitorId: false,                         // throw if visitor ID missing
  idempotencyStore: new InMemoryIdempotencyStore(), // dedupe webhooks (7-day TTL)
  onError: (err, ctx) => console.error(err, ctx),  // error callback

  // Retry (defaults shown; set to false to disable)
  retry: {
    maxAttempts: 3,
    initialDelayMs: 1000,
    maxDelayMs: 16000,
    retryableStatusCodes: [408, 429, 500, 502, 503, 504],
  },

  // Dead letter callback — fires after all retries fail
  onDeadLetter: async ({ eventType, eventId, transactionId, payment, error, attempts }) => {
    console.error(`Dead letter: ${eventType} ${eventId} failed after ${attempts} attempts`, error);
    // Save to DB, send to Slack, push to a queue, etc.
  },

  // Event filtering — only process these event types
  eventFilter: ['checkout.completed', 'subscription.paid', 'refund.created'],

  // Dry-run mode — logs what would be sent, doesn't call DataFast
  dryRun: false,

  // Transaction hydration — fetch full order from CREEM API before mapping
  hydrateTransaction: false,
});

Client-side cookie helper

In the browser, read the DataFast visitor ID cookie and include it when creating a checkout:

// Import the browser-specific bundle (no Node.js dependencies)
import {
  getDataFastVisitorId,
  buildCheckoutAttributionMetadata,
  autoAttributeCheckoutLinks,
} from 'creem-datafast-sdk/browser';

// Read the cookie
const visitorId = getDataFastVisitorId();
// => "3cff4252-fa96-4gec-8b1b-bs695e763b65" or null

// Or build metadata that includes it automatically
const metadata = buildCheckoutAttributionMetadata({ campaign: 'spring' });
// => { campaign: 'spring', datafast_visitor_id: '3cff4252-...' }

Send the visitorId to your server when creating a checkout.

Auto-attribute checkout links

Automatically append the DataFast visitor ID to all CREEM checkout links on the page:

import { autoAttributeCheckoutLinks } from 'creem-datafast-sdk/browser';

// Call after DOM is ready
document.addEventListener('DOMContentLoaded', () => {
  const count = autoAttributeCheckoutLinks();
  console.log(`Attributed ${count} checkout links`);
});

// Or with a custom selector
autoAttributeCheckoutLinks('a[href*="checkout.creem.io"], a.checkout-btn');

This finds all <a> tags matching the selector and appends metadata[datafast_visitor_id]=<visitor_id> as a query parameter.

Zero-config script tag

For static sites, add the data-auto-init attribute to auto-attribute links on page load:

<script src="https://unpkg.com/creem-datafast-sdk/dist/browser.js" data-auto-init></script>

Checkout example

const checkout = await cd.createCheckout({
  input: {
    productId: 'prod_xxx',
    customer: { email: '[email protected]' },
    successUrl: 'https://yoursite.com/success',
    metadata: { campaign: 'spring' },
  },
  datafastVisitorId: visitorId, // from client-side cookie
});

// checkout.checkoutUrl => redirect the customer here

The visitor ID is stored in metadata.datafast_visitor_id. Your existing metadata keys are preserved.

Checkout URL builder (static sites)

For static sites or JAMstack apps that can't make server-side calls, build a checkout URL with attribution baked in:

import { buildCheckoutUrl } from 'creem-datafast-sdk';

const url = buildCheckoutUrl({
  productId: 'prod_xxx',
  datafastVisitorId: 'vis_abc123',
  queryParams: { coupon: 'SPRING20' }, // optional extra params
});
// => "https://checkout.creem.io?product_id=prod_xxx&metadata[datafast_visitor_id]=vis_abc123&coupon=SPRING20"

// Redirect or use in an <a> tag
window.location.href = url;

Merge strategies

If datafast_visitor_id already exists in metadata:

| Strategy | Behavior | |----------|----------| | "preserve" (default) | Keep the existing value | | "overwrite" | Replace with the new value | | "error" | Throw MetadataCollisionError |

const checkout = await cd.createCheckout({
  input: { productId: 'prod_xxx', metadata: { datafast_visitor_id: 'old' } },
  datafastVisitorId: 'new',
  mergeStrategy: 'overwrite', // "new" wins
});

Express example

import express from 'express';
import { createCreemDataFast } from 'creem-datafast-sdk';

const app = express();
const cd = createCreemDataFast({ /* config */ });

// Important: use express.raw() on the webhook route, NOT express.json()
app.post(
  '/webhook/creem',
  express.raw({ type: 'application/json' }),
  cd.expressWebhookHandler({
    onProcessed(result) {
      console.log('Payment tracked:', result.transactionId);
    },
    onError(error) {
      console.error('Webhook failed:', error);
    },
  })
);

Next.js example

// app/api/webhook/creem/route.ts
import { createCreemDataFast } from 'creem-datafast-sdk';

const cd = createCreemDataFast({ /* config */ });

export const POST = cd.nextWebhookHandler({
  onProcessed(result) {
    console.log('Payment tracked:', result.transactionId);
  },
});

Works with both Node.js and Edge runtime thanks to the Web Crypto API.

Webhook signature verification

CREEM signs webhooks with HMAC SHA256 using the creem-signature header. This package:

  1. Reads the raw request body (not re-serialized JSON)
  2. Computes HMAC SHA256 using the Web Crypto API (crypto.subtle) — compatible with Edge runtimes
  3. Compares using constant-time comparison to prevent timing attacks
  4. Rejects with InvalidWebhookSignatureError on mismatch

Critical: For Express, use express.raw({ type: 'application/json' }) on the webhook route so the raw body is preserved. Using express.json() will re-serialize the body and break signature verification.

You can also use the verifier standalone:

import { verifyCreemWebhookSignature } from 'creem-datafast-sdk';

// Note: this is async (returns a Promise)
await verifyCreemWebhookSignature(rawBody, signatureHeader, webhookSecret);
// throws InvalidWebhookSignatureError if invalid

Event support matrix

| CREEM Event | DataFast Field Mapping | |---|---| | checkout.completed | amount, currency, transaction_id (order ID), email, name, customer_id, datafast_visitor_id (from metadata), renewal: false | | subscription.paid | Same fields, renewal: true, timestamp from period start | | refund.created | Same fields, negative amount, refunded: true, timestamp from refund |

Unsupported CREEM events are silently acknowledged (HTTP 200) so CREEM does not retry them.

Retry and dead letter handling

By default, DataFast API calls are retried up to 3 times with exponential backoff and jitter on transient failures (5xx, 408, 429, network errors). Non-retryable errors (4xx) fail immediately.

const cd = createCreemDataFast({
  // ...

  // Custom retry config
  retry: {
    maxAttempts: 5,          // up to 5 attempts
    initialDelayMs: 500,     // start at 500ms
    maxDelayMs: 30000,       // cap at 30s
    retryableStatusCodes: [429, 500, 502, 503, 504],
  },

  // Or disable retries entirely
  // retry: false,

  // Called when all retries fail — use for alerting or manual replay queues
  onDeadLetter: async (ctx) => {
    await db.deadLetters.insert({
      eventId: ctx.eventId,
      eventType: ctx.eventType,
      transactionId: ctx.transactionId,
      payload: ctx.payment,
      error: ctx.error.message,
      attempts: ctx.attempts,
      failedAt: new Date(),
    });
    await slack.alert(`Dead letter: ${ctx.eventType} ${ctx.eventId}`);
  },
});

Webhook replay

Re-process a previously failed webhook. This bypasses idempotency checks so the event is processed again even if it was already recorded:

// Replay from stored raw body and headers
const result = await cd.replayWebhook({
  rawBody: storedRawBody,
  headers: storedHeaders,
});

console.log(result.ok, result.transactionId);

Useful for ops recovery when events end up in the dead letter queue.

Dry-run mode

Test your webhook pipeline without sending data to DataFast:

const cd = createCreemDataFast({
  // ...
  dryRun: true,
});

In dry-run mode, the full pipeline runs (signature verification, event parsing, payment mapping) but the DataFast API call is skipped. The mapped payment payload is logged instead. The handler returns { skipped: true, skipReason: 'dry_run' }.

Webhook event filtering

Only process the events you care about:

const cd = createCreemDataFast({
  // ...
  eventFilter: ['checkout.completed'], // ignore subscription.paid and refund.created
});

Events not in the filter are silently acknowledged (HTTP 200, skipReason: 'filtered'). If eventFilter is not set, all supported events are processed.

Transaction hydration

By default, payment data is extracted from the webhook payload. Enable hydration to fetch the full order details from the CREEM API before mapping — this can provide richer data (e.g., additional metadata, updated amounts):

const cd = createCreemDataFast({
  // ...
  hydrateTransaction: true,
});

This adds a CREEM API call to the webhook processing path, which increases latency. If the API call fails, the handler falls back to the webhook payload data and logs a warning.

Health check

Verify all integration connections in one call:

const health = await cd.healthCheck();

console.log(health);
// {
//   healthy: true,
//   checks: {
//     creemApiKey: { ok: true, message: 'CREEM API key is configured' },
//     webhookSecret: { ok: true, message: 'Webhook secret is configured' },
//     datafastApi: { ok: true, message: 'DataFast API reachable (HTTP 200)', latencyMs: 142 },
//   },
//   timestamp: '2025-06-15T10:30:00.000Z',
// }

Use this in a /health endpoint for deploy verification or monitoring dashboards.

Idempotency

CREEM retries webhook deliveries. To prevent duplicate DataFast payments, use an idempotency store:

In-memory store (with TTL)

import { createCreemDataFast, InMemoryIdempotencyStore } from 'creem-datafast-sdk';

const store = new InMemoryIdempotencyStore({
  ttlMs: 7 * 24 * 60 * 60 * 1000,   // 7 days (default)
  cleanupIntervalMs: 60 * 60 * 1000, // cleanup every hour (default)
});

const cd = createCreemDataFast({
  // ...
  idempotencyStore: store,
});

// Call on shutdown to stop the cleanup timer
process.on('SIGTERM', () => store.dispose());

Entries are lazily evicted on access and periodically cleaned up. The cleanup timer is unref'd so it won't prevent process exit.

Upstash Redis store (production)

import { Redis } from '@upstash/redis';
import { createCreemDataFast, UpstashIdempotencyStore } from 'creem-datafast-sdk';

const redis = new Redis({
  url: process.env.UPSTASH_REDIS_REST_URL!,
  token: process.env.UPSTASH_REDIS_REST_TOKEN!,
});

const cd = createCreemDataFast({
  // ...
  idempotencyStore: new UpstashIdempotencyStore({
    redis,
    keyPrefix: 'creem-df:idempotency:', // default
    ttlSeconds: 604800,                  // 7 days (default)
  }),
});

Works with @upstash/redis or any client implementing get(key) and set(key, value, { ex }).

Custom store

Implement the IdempotencyStore interface with any backend:

import type { IdempotencyStore } from 'creem-datafast-sdk';

class PostgresIdempotencyStore implements IdempotencyStore {
  async has(key: string): Promise<boolean> {
    const row = await db.query('SELECT 1 FROM webhook_events WHERE id = $1', [key]);
    return row.length > 0;
  }
  async set(key: string, value: { processedAt: string; eventType: string }): Promise<void> {
    await db.query(
      'INSERT INTO webhook_events (id, processed_at, event_type) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING',
      [key, value.processedAt, value.eventType]
    );
  }
}

Dedupe keys are based on the webhook event ID.

Batch payment API

Backfill historical CREEM transactions into DataFast:

import { DataFastClient } from 'creem-datafast-sdk';

const client = new DataFastClient({
  apiKey: process.env.DATAFAST_API_KEY!,
  baseUrl: 'https://datafa.st',
  timeoutMs: 30_000,
  fetch: globalThis.fetch,
});

const { results } = await client.sendPayments([
  { amount: 49.00, currency: 'USD', transaction_id: 'ord_001', datafast_visitor_id: 'vis_aaa' },
  { amount: 99.00, currency: 'USD', transaction_id: 'ord_002', datafast_visitor_id: 'vis_bbb' },
  { amount: 29.00, currency: 'EUR', transaction_id: 'ord_003' },
]);

for (const r of results) {
  if (r.ok) {
    console.log('Sent:', r.response?.status);
  } else {
    console.error('Failed:', r.error?.message);
  }
}

Payments are sent sequentially with retry. Each result indicates success or failure independently.

DataFast payments query

Look up payments by visitor ID to debug attribution gaps:

import { DataFastClient } from 'creem-datafast-sdk';

const client = new DataFastClient({
  apiKey: process.env.DATAFAST_API_KEY!,
  baseUrl: 'https://datafa.st',
  timeoutMs: 10_000,
  fetch: globalThis.fetch,
});

const { body } = await client.getPayments('vis_abc123');
console.log(body); // DataFast API response with matching payments

Troubleshooting

Signature verification fails

  • Ensure you're using express.raw() not express.json() on the webhook route
  • Verify your webhook secret matches what's configured in CREEM
  • Check that no middleware is modifying the request body before the webhook handler
  • Note: verifyCreemWebhookSignature() is now async — make sure you await it

Missing visitor ID warnings

  • Ensure the DataFast tracking script is loaded on your site
  • Check that the datafast_visitor_id cookie exists before creating the checkout
  • The cookie is set by DataFast's client-side script, not by this package

Duplicate payments in DataFast

  • Enable the idempotency store
  • Use a durable store (Redis/Upstash) in production — the in-memory store resets on restart

DataFast API failures

  • Check the onDeadLetter callback for events that failed after all retries
  • Use cd.replayWebhook() to re-process events from the dead letter queue
  • Run cd.healthCheck() to verify DataFast API connectivity

Edge runtime errors

  • This package uses the Web Crypto API — no node:crypto dependency
  • Ensure your runtime supports crypto.subtle (Node.js 18+, Vercel Edge, Cloudflare Workers, Deno)

Security notes

  • Webhook signatures are verified using constant-time comparison (prevents timing attacks)
  • Uses the Web Crypto API — compatible with Edge runtimes without node:crypto
  • Secrets are never logged, even in debug mode
  • Raw bodies are used for verification, never re-serialized JSON
  • The package validates all required config at initialization time
  • The DataFast API key is sent as a Bearer token over HTTPS

Error types

| Error | When | |---|---| | ConfigError | Missing required config fields | | InvalidWebhookSignatureError | Bad or missing webhook signature | | UnsupportedWebhookEventError | Event type not in supported set | | MissingVisitorIdError | No visitor ID and requireVisitorId: true | | MetadataCollisionError | Visitor ID exists and merge strategy is "error" | | DataFastRequestError | DataFast API returns non-2xx after all retries (includes .status and .responseBody) |

API reference

createCreemDataFast(config)CreemDataFastInstance

| Method | Description | |---|---| | createCheckout(options) | Create a CREEM checkout with DataFast attribution | | handleWebhook(input) | Process a raw webhook event | | replayWebhook(input) | Re-process a webhook, bypassing idempotency | | expressWebhookHandler(options?) | Express middleware for CREEM webhooks | | nextWebhookHandler(options?) | Next.js App Router handler for CREEM webhooks | | buildCheckoutUrl(options) | Build a CREEM checkout URL with attribution | | healthCheck() | Verify all integration connections | | creem | The underlying CREEM SDK client |

DataFastClient

| Method | Description | |---|---| | sendPayment(payment) | Send a single payment event (with retry) | | sendPayments(payments[]) | Send multiple payments sequentially | | getPayments(visitorId) | Query payments by visitor ID |

Browser exports (creem-datafast-sdk/browser)

| Function | Description | |---|---| | getDataFastVisitorId() | Read the visitor ID cookie | | buildCheckoutAttributionMetadata(existing?) | Build metadata with visitor ID | | autoAttributeCheckoutLinks(selector?) | Append visitor ID to checkout links |

Development

# Install dependencies
npm install

# Build
npm run build

# Run tests
npm test

# Watch tests
npm run test:watch

# Typecheck
npm run typecheck

# Run Express demo
cd examples/express-demo && npm install && npm run dev

License

MIT