How to add plans, checkout, and paywalls to a job board

Present employer plans, posting products, candidate offers, checkout, and entitlement return states.

Cavuno is authoritative for products, prices, checkout requirements, and entitlements. Your frontend presents those decisions and owns the return route. Never hard-code a price or infer access from a successful redirect alone.

Present employer plans

ts
const [plans, salesLed] = await Promise.all([
board.plans.list(),
board.plans.salesLed(),
]);

Render the returned currency, billing interval, limits, and call to action. A sales-led plan should enter the returned contact path rather than a checkout intended for self-service products.

Build public job-posting checkout

Load board.jobPosting.plans(), collect the posting fields, and complete any required billing-email verification before calling board.jobPosting.create(). Use checkBilling, sendBillingVerification, and getBillingOptions for that pre-submission verification flow. The create result is a discriminated union: narrow on status before reading status-specific fields.

ts
const result = await board.jobPosting.create(input);
switch (result.status) {
case 'checkout':
return redirect(result.checkoutUrl);
case 'published':
return { step: 'published', jobSlug: result.jobSlug };
case 'pending_approval':
return { step: 'pending-approval', jobId: result.jobId };
case 'invoice_sent':
return { step: 'invoice-sent', jobId: result.jobId };
default: {
const unhandled: never = result;
throw new Error(`Unhandled posting result: ${String(unhandled)}`);
}
}

Do not publish a success page solely because the payment provider redirected back. Retrieve the posting or entitlement state from Cavuno.

Build candidate paywall offers

ts
const offers = await board.paywall.offers();
const access = await board.me.access(undefined, {
cache: 'no-store',
headers: { authorization: `Bearer ${accessToken}` },
});

Use board.me.access.checkout to start checkout, retrieveCheckout on return, and portal for subscription management. The access response decides whether gated candidate data may render.

Keep checkout routes private and replay-safe

Return routes can be revisited. Read current state every time and render the same final outcome for an already-completed checkout. Do not store an access grant only in browser state. Never cache checkout, portal, or entitlement responses publicly.

Verify monetization

  1. Render every returned currency and interval without client-side price conversion.
  2. Exercise free, self-service paid, sales-led, billing-verification, canceled, and completed states available on the development board.
  3. Reload the checkout return route and confirm it remains correct and does not create another purchase.
  4. Sign in without candidate access and confirm gated data is absent from HTML and API responses.
  5. Open the customer portal, change access, and confirm the next account load reflects the new server entitlement.

Finish with Build an SEO-complete board to connect every public route into one crawlable system.