Integration Guide
Step-by-step setup for any Next.js, Node, CLI, or browser extension project.
Credentials
Two values provided by the operator out-of-band.
| Value | Env var | Description |
|---|---|---|
app_id | NEXT_PUBLIC_SPARK_APP_ID | Public app identifier |
Webhook secret | SPARK_PAY_WEBHOOK_SECRET | HMAC signature verification (push mode only) |
Payment Flow
End-to-end flow from your app to Stripe and back.
User clicks "Upgrade" in your app
Redirect to sparkpay.dev pricing page with email & return_url
User picks plan, pays on Stripe Checkout
SparkPay records subscription in its database
(Push mode) SparkPay fires outgoing webhook to your app
User redirected to your return_url
Your success page polls status until active
Gate features via status endpoint
Step 0: Define Your Plans
Configure plans in the dashboard or via API. Each plan becomes a card on your pricing page.
| Field | Type | Description |
|---|---|---|
name | string | Display name on the pricing card |
tier | string | URL-safe slug (auto-generated from name) |
payment_type | string | subscription | one_time | free | contact |
features | string[] | Bullet points on the pricing card |
limits | Record<string, number> | Machine-readable caps your app enforces |
recommended | boolean | Highlights as the default choice |
trial_days | number | Per-plan trial override (0 = no trial) |
Step 1: Download Your Integration Kit
Pre-configured files generated from your live plan configuration.
NEXT_PUBLIC_ env vars + fetch revalidation
Plain SPARK_APP_ID + in-memory cache
Hardcoded APP_ID + in-memory cache
Step 2: Send Users to the Pricing Page
Redirect unauthenticated or unpaid users to your hosted pricing page.
Next.js
import { pricingUrl } from '@/lib/SparkPay-Integration';import { redirect } from 'next/navigation';redirect(pricingUrl({email: user.email,returnUrl: `${process.env.NEXT_PUBLIC_APP_URL}/success`}));
Step 3: Handle the Success Page
Poll the status endpoint every 2s until the subscription is active, then redirect.
app/success/page.tsx
import { PaymentPoller } from './PaymentPoller';export default async function SuccessPage({searchParams,}: {searchParams: Promise<{ email?: string }>;}) {const { email } = await searchParams;if (!email) redirect('/dashboard');return <PaymentPoller email={email} />;}
app/success/PaymentPoller.tsx
'use client';import { useEffect, useState } from 'react';import { useRouter } from 'next/navigation';import { getStatus, isPaid } from '@/lib/SparkPay-Integration';const POLL_INTERVAL_MS = 2000;const MAX_ATTEMPTS = 8; // 16 seconds totalexport function PaymentPoller({ email }: { email: string }) {const router = useRouter();const [timedOut, setTimedOut] = useState(false);useEffect(() => {let attempts = 0;let cancelled = false;async function poll() {if (cancelled) return;try {const data = await getStatus(email);if (isPaid(data)) { router.replace('/dashboard'); return; }} catch {}if (++attempts >= MAX_ATTEMPTS) { setTimedOut(true); return; }setTimeout(poll, POLL_INTERVAL_MS);}poll();return () => { cancelled = true; };}, [email, router]);if (timedOut) return <p>Payment received. Access may take a moment to activate.</p>;return <p>Processing your payment...</p>;}
Step 4: Gate Features
The gating helpers work identically across all platforms.
import { auth } from '@/auth';import { getStatusCached, isPaid, pricingUrl } from '@/lib/SparkPay-Integration';import { redirect } from 'next/navigation';export default async function DashboardPage() {const session = await auth();if (!session?.user?.email) redirect('/login');const data = await getStatusCached(session.user.email);if (!isPaid(data)) redirect(pricingUrl({ email: session.user.email }));return <div>Welcome to your dashboard</div>;}
Step 5: Badge Component
Drop-in React component showing plan status and upgrade CTA.
import { SparkBadge } from '@/lib/SparkPay-Badge';<SparkBadge email={user.email} />
Step 6: Webhooks (Push Mode)
Optional. React to events in real time. Requires a server endpoint.
{"event": "subscription.activated","app_id": "your-app-id","email": "user@example.com","timestamp": "2026-03-14T12:00:00.000Z","subscription": {"status": "active","payment_type": "subscription","price_id": "price_xxx","current_period_end": "2026-04-14T12:00:00.000Z"}}
| Event | When it fires |
|---|---|
| checkout.completed | Payment captured; first record created |
| subscription.activated | Recurring sub is active (also fires on trial convert) |
| subscription.trial_started | User began a free trial |
| subscription.past_due | Payment failed; grace period started |
| subscription.canceled | Subscription ended |
| referral.completed | Referral processed; reward coupon created |
Key Points
Gate on access.is_paid or access.tier - not on current_period_end. Server-computed access flags are authoritative.
is_lifetime: true means permanent access - no cancellation event will ever fire.
No database tables needed - SparkPay is your single source of truth.
Free-tier users have subscription: null - but receive plan with features and limits if a free plan is configured.
Tier resolution happens server-side - plan tells you what they bought; access tells you what they can do now.
What NOT to Build
Do not create subscriptions, payments, or plans tables in your database.
Do not store status, price_id, or current_period_end locally.
Do not try to sync or mirror SparkPay's subscription state.
If you need faster reads, use getStatusCached() (60s revalidation). That is all.
Ready to ship?
Add Stripe payments to your app in minutes. $99, one-time purchase.