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

@crvouga/mockingbird-service-firstpromoter

v1.0.2

Published

Stateful mock of the FirstPromoter v2 API: promoters (adopt-before-create by cust_id), signup tracking by tid / promoter_id / ref token, iframe login, archive, and Basic-auth lead_becomes_referral webhooks.

Readme

@crvouga/mockingbird-service-firstpromoter

Stateful mock of the FirstPromoter v2 affiliate API for test suites: promoter create (adopt before create by cust_id), lookups by id / cust_id / ref_token / email, list, update, archive, the dashboard iframe login, signup tracking by click tid, promoter_id or ref token, and the Basic-auth lead_becomes_referral webhook our backend receives. Every request is recorded in the journal, so a checkout suite can assert what the post-checkout job sent instead of producing a failed job.

  • Operation coverage: SUPPORT.md
  • FirstPromoter publishes no machine-readable spec: the contract (openapi.yaml) is hand-authored from our consumer's requests and the zod schemas it validates responses with.

Install

npm install -D @crvouga/mockingbird-service-firstpromoter

ESM only. Node >= 22 or Bun >= 1.2. No native dependencies. Serve it with npx mockingbird-firstpromoter serve, createServer from ./server (Node), or createRuntime with any Fetch server.

Usage

Point FIRST_PROMOTER_API_URL at the mock (our Joi schema requires https, so either front it with TLS or relax that rule in the stack), set any FIRST_PROMOTER_API_KEY and FIRST_PROMOTER_ACCOUNT_ID, and pass the webhook Basic-auth pair as --webhook-secret.

npx mockingbird-firstpromoter serve --port 8812 \
  --webhook-url http://127.0.0.1:3000/users/webhooks/first-promoter \
  --webhook-secret "$FIRST_PROMOTER_WEBHOOK_AUTH_USERNAME:$FIRST_PROMOTER_WEBHOOK_AUTH_PASSWORD"
import { createRuntime } from "@crvouga/mockingbird-service-firstpromoter"

const fp = createRuntime({
  webhooks: { url: "http://127.0.0.1:3000/users/webhooks/first-promoter", secret: "user:pass" },
})
const admin = (path: string, body: unknown) =>
  fp.fetch(
    new Request(`http://fp.test/__admin${path}`, {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify(body),
    }),
  )

// An existing affiliate, and a click on their link: the tid checkout stores as fp_tid.
await admin("/promoters", { email: "[email protected]", ref_token: "nate91" })
const { tid } = (await (await admin("/clicks", { ref_token: "nate91" })).json()) as { tid: string }
// …the app's invoice handler posts POST /v2/track/signup {email, tid}; the mock converts the
// referral and posts lead_becomes_referral to the backend with Authorization: Basic…

Routes

| Route | Behaviour | | --- | --- | | POST /v2/company/promoters | {email, cust_id?, profile?: {first_name, last_name}, drip_emails?, initial_campaign_id?} → the promoter (FirstPromoterV2ResponseSchema), enrolled in the default campaign with a ref_token (first name + digits) and ref_link https://acme.example/referrals?fpr=<token>. A live promoter with the same email or cust_id is 422 {message, errors: {email: ["has already been taken"]}}. | | GET /v2/company/promoters?page=&per_page= | {data: [promoter], meta: {pending_count}}; archived promoters are left out. | | GET /v2/company/promoters/{value}?find_by=id\|cust_id\|ref_token\|email | One promoter (archived ones too, with archived_at), or 404 {message: "Promoter not found"}. | | PUT /v2/company/promoters/{id} | {cust_id?, email?, note?, profile?} → the promoter; 404 / 422 as above. | | POST /v2/company/promoters/archive | {ids: [..]} → a completed batch {id, status, total, selected_total, processed_count, failed_count, action_label, progress, processing_errors, …}. | | POST /v2/promoters/iframe_login?promoter_id= | {access_token, expires_in}, or 404. | | POST /v2/track/signup | {email, tid? \| promoter_id? \| ref_id?, uid?, skip_email_notification?} → the referral. An unknown tid, promoter or ref token is 404; none of them is 400; an email already tracked is 422. With autoConvert (default on) the referral becomes a customer at once and lead_becomes_referral is posted. |

Every call needs Authorization: Bearer <key> and an Account-ID header (else 401).

Webhooks

lead_becomes_referral bodies match our FirstPromoterNewCustomerWebhookSchema: {event: {id, type, created_at}, data: {id, state, email, customer_since, promotion: {id, promoter_id, ref_id, leads_count, customers_count, current_referral_reward}, promoter: {id, cust_id, email, earnings_balance: {cash}}}}, sent with Authorization: Basic base64(<secret>) where the secret is user:pass. Non-2xx answers are retried; GET /__admin/webhooks, …/events, …/flush and PUT /__admin/webhook-endpoints work as usual.

Admin (beyond the standard contract)

| Route | Effect | | --- | --- | | POST /__admin/promoters | Seed a promoter {email, cust_id?, first_name?, last_name?, ref_token?, campaign_id?}. | | GET /__admin/promoters | The namespace's promoters (stored form). | | POST /__admin/clicks | {ref_token} → {tid, promoterId}: a click on the promoter's link. | | GET /__admin/referrals | Tracked signups. | | POST /__admin/referrals/:id/convert | {saleAmount?}: the lead paid; credits the promoter (percent reward × amount) and posts the webhook. | | GET\|PUT /__admin/settings | {website?, defaultCampaignId?, autoConvert?, campaigns?} (campaigns carry referralRewards / promoterRewards with coupon, amount, unit, per_of_sale). The default campaign's referral coupon is ACME50. |

Fault presets (POST /__admin/faults {"preset": "<name>", "count"?: n}; GET /__admin/faults/presets): created_but_500 (stored, then 500: the retry must adopt by cust_id), lookup_unavailable (lookups 503: neither create nor adopt), no_campaign (no promoter_campaigns: no ref link), unauthorized, rate_limited, server_error, connection_drop, webhook_duplicate, webhook_drop.

Namespaces

x-mockingbird-namespace, a /ns/<name> prefix in FIRST_PROMOTER_API_URL, or by API key: PUT /__admin/credentials {"credentials": {"<FIRST_PROMOTER_API_KEY>": "<namespace>"}}.

Deliberately not modelled

  • Error bodies and exact status codes of the real API are unverified (no sandbox credentials): errors use {message} and validation {message, errors: {field: [...]}}. Our consumer only branches on ok / 404, which the mock gets right.
  • The v1 API our EMR backend still calls (/v1/promoters/*, /v1/track/signup, /v1/reports/campaigns); the catalog scopes this mock to v2.
  • Sales, refunds, commissions and payouts beyond the single convert step; fraud checks; promo codes; the hosted affiliate portal behind the iframe token.
  • Promoter ids are sequential from 4800001 per namespace.

API

| Export | Kind | Description | | --- | --- | --- | | FirstPromoterAPI | class | The in-process mock: fetch(request), reset(), render(promoter), click(refToken), convert(referralId, saleAmount?), seedPromoter(input), state. Options: sqlite, now, namespace, settings, onWebhook. | | createRuntime | function | The mock with the full service contract (health, admin, namespaces, credentials, presets, webhooks). Options: webhooks: {url, secret, retryDelaysMs?, fetch?}, settings, clock, seed, adminKey, onLog, sqlite. | | FIRSTPROMOTER_PRESETS | object | Every named fault preset. | | FIRSTPROMOTER_NAMESPACE | string | The service name, "firstpromoter". | | WEBHOOK_PATH | string | Our receiver's path, /users/webhooks/first-promoter. | | DEFAULT_CAMPAIGNS, DEFAULT_SETTINGS | values | The seeded campaigns and settings. | | document, operationIds, supportedOperationIds | values | The vendored OpenAPI contract and its operation ids. | | createServer, serveTarget, DEFAULT_PORT (./server) | Node | Serve over node:http; the serve CLI target; port 8812. |

Part of mockingbird.