Sale tracking

Record successful purchases — the event that creates a commission.

Sale tracking is what creates a commission. There are three ways to get a sale event into Partli, easiest first:

  1. Connect your Stripe account via OAuth — no code, we listen for paid checkouts and invoices automatically.
  2. Use the JS snippet + your own Stripe webhook (forward sales to our REST API).
  3. POST to the REST API directly from any backend.

This page documents option 3 — the underlying API call. Options 1 and 2 also end up calling this same endpoint internally.

POST /api/track/sale

await fetch("https://partli.app/api/track/sale", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.PARTLI_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    externalId: "inv_abc123",         // your invoice id (used for dedupe)
    customerExternalId: "cus_xyz",    // your customer id
    amountCents: 4900,                // sale amount in cents ($49.00)
    currency: "USD",                  // optional, defaults to USD
    clickId: cookies.get("pref_cid"), // attribution cookie
    eventName: "subscription_paid",   // optional, for your reporting
    metadata: { plan: "starter" },    // optional, free-form jsonb
  }),
});

What happens after the call

  1. We resolve clickId to a partner. No attribution → no-op.
  2. We upsert a customer record keyed on customerExternalId.
  3. We create the sale event row.
  4. We resolve the right reward (group-level → program-level fallback).
  5. For one-time interval: skip if a prior commission exists for this customer.
  6. For recurring: skip if past the duration cap.
  7. Calculate amountCents and create a pending commission with payable_at = now + holdingPeriodDays.
  8. Fire sale.created + commission.created webhooks.

Idempotent by externalId

Re-posting the same externalId is a no-op. Safe to call from a Stripe webhook handler that may retry on transient failures.

Stripe webhook → track/sale

The simplest integration: in your Stripe webhook handler, listen forinvoice.paid (or checkout.session.completed) and forward to /api/track/sale:

// Inside your Stripe webhook handler
if (event.type === "invoice.paid") {
  const invoice = event.data.object;
  // Pull the click ID from invoice metadata, customer metadata, or
  // your own DB lookup (you stored it on signup)
  const clickId = invoice.metadata.partli_click_id;

  await fetch("https://partli.app/api/track/sale", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.PARTLI_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      externalId: invoice.id,
      customerExternalId: invoice.customer,
      amountCents: invoice.amount_paid,
      currency: invoice.currency.toUpperCase(),
      clickId,
    }),
  });
}

Refund handling

Automated clawback via webhook isn't live yet. If a sale is refunded, find the related commission in your dashboard and mark it as clawed back manually. As long as the commission is still in pending or approved (not yet paid out), no money has moved.