FauryaFaurya
Revenue attribution

Paddle Checkout API

Track and attribute revenue in Faurya for Paddle Checkout API.

Faurya can automatically attribute revenue to sessions, campaigns, and sources by connecting your payment flow to your Faurya site. This guide shows the safest, most reliable way to do that with Paddle.

What “revenue attribution” means in Faurya

When a visitor lands on your site, the Faurya script assigns a Visitor ID and Session ID. When the visitor starts checkout, you pass those IDs (or the Faurya attribution token) into your payment provider. When the payment succeeds, your server (or webhook handler) confirms the payment and notifies Faurya. Faurya then ties the purchase back to the original session and attributes revenue to the right channel.

  • Create a Paddle checkout/session server-side
  • Include Faurya attribution fields in metadata/custom data
  • Listen to Paddle webhooks to confirm payment and record revenue to Faurya

Environment variables

Add these to your environment (Vercel / Render / Docker / .env):

  • FAURYA_SITE_ID
  • FAURYA_WRITE_KEY
  • PADDLE_API_KEY
  • PADDLE_WEBHOOK_SECRET

Frontend: collect attribution IDs

On your marketing / pricing page, the Faurya script is already loaded. You can read the IDs and attach them to your checkout creation request.

// example: Next.js client component
function getFauryaAttribution() {
  // Your tracking script can expose these in different ways.
  // Use whichever your implementation supports.
  const w = window as any;
  return {
    faVisitorId: w.__FAURYA_VISITOR_ID__ ?? null,
    faSessionId: w.__FAURYA_SESSION_ID__ ?? null,
    faAttributionToken: w.__FAURYA_ATTRIBUTION_TOKEN__ ?? null,
  };
}

async function startCheckout(planId: string) {
  const attribution = getFauryaAttribution();
  const res = await fetch("/api/checkout/create", {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({ planId, ...attribution }),
  });
  const data = await res.json();
  window.location.href = data.url;
}

Create checkout (server)

export async function POST(req: Request) {
  const body = await req.json();
  const { priceId, faVisitorId, faSessionId, faAttributionToken } = body;
  const orderId = crypto.randomUUID();

  // Pseudo request — adapt to your Paddle Billing API version
  const checkout = await fetch("https://api.paddle.com/checkouts", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.PADDLE_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      items: [{ price_id: priceId, quantity: 1 }],
      custom_data: {
        orderId,
        faVisitorId: faVisitorId ?? "",
        faSessionId: faSessionId ?? "",
        faAttributionToken: faAttributionToken ?? "",
      },
      success_url: `${process.env.APP_URL}/billing/success`,
      cancel_url: `${process.env.APP_URL}/billing/cancel`,
    }),
  }).then(r => r.json());

  return Response.json({ url: checkout.url });
}

Webhook → record revenue

Validate signature, extract custom data, call Faurya.

Webhook security checklist

If you use webhooks (recommended), make sure you:

  • Validate the webhook signature using the provider’s official method
  • Enforce HTTPS
  • Return 2xx quickly; do expensive work async if needed
  • Log the raw payload + signature header for debugging
  • Treat webhooks as “at least once” delivery (use idempotency)

Backend: record the purchase in Faurya

The rule: only record revenue after your server verifies payment success (webhook or provider API verification). That keeps attribution accurate and prevents spoofed events.

Example call to Faurya (pseudo-API):

// example: Node / Next.js route handler
async function recordRevenueToFaurya(input: {
  siteId: string;
  writeKey: string;
  amount: number;        // in minor units (e.g., cents/paise) unless you decide otherwise
  currency: string;      // "USD", "INR", ...
  orderId: string;       // your unique id
  provider: "paddle";
  providerPaymentId: string;
  faVisitorId?: string | null;
  faSessionId?: string | null;
  faAttributionToken?: string | null;
}) {
  const res = await fetch("https://YOUR_FAURYA_DOMAIN/api/revenue", {
    method: "POST",
    headers: {
      "content-type": "application/json",
      "x-faurya-site-id": input.siteId,
      "x-faurya-write-key": input.writeKey,
    },
    body: JSON.stringify({
      event: "purchase",
      amount: input.amount,
      currency: input.currency,
      orderId: input.orderId,
      provider: input.provider,
      providerPaymentId: input.providerPaymentId,
      attribution: {
        faVisitorId: input.faVisitorId,
        faSessionId: input.faSessionId,
        faAttributionToken: input.faAttributionToken,
      },
    }),
  });

  if (!res.ok) throw new Error(`Faurya revenue call failed: ${res.status}`);
}

Idempotency (do this to avoid double counting)

Always make your revenue writes idempotent:

  • Use a unique orderId from your DB or
  • Use the provider’s unique payment/transaction ID as the idempotency key
  • Store a “revenue_recorded_at” timestamp on your order row so repeated webhook deliveries don’t duplicate revenue

Troubleshooting

Revenue shows as “unattributed”:

  • Make sure you are passing faSessionId or faAttributionToken from the browser to your server when you create checkout.
  • Confirm your checkout/session metadata includes that value.
  • Confirm your webhook handler reads it back and sends it to Faurya.

Revenue is duplicated:

  • Ensure you’re not recording revenue both on “return URL” and on webhook.
  • Add idempotency using orderId / provider transaction ID.

Currency or amount looks wrong:

  • Decide one convention (minor units recommended) and keep it consistent.
  • Always store currency codes in ISO 4217 (USD/INR/EUR).

On this page