# How to Integrate Bolt.new with Podia

- Tool: Bolt.new
- Difficulty: Intermediate
- Time required: 25 minutes
- Last updated: April 2026

## TL;DR

To integrate Bolt.new with Podia, use the Podia API (available on Mover plan and above) to fetch customer and subscription data, then build a custom student dashboard or course access portal in Bolt. For free plan users without API access, embed Podia's checkout links or JavaScript widget. Podia webhooks for enrollment events require a deployed URL — the Bolt WebContainer cannot receive incoming connections.

## Build a Custom Student Portal for Your Podia Courses with Bolt.new

Podia is the all-in-one storefront for creators selling courses, digital downloads, and memberships. While Podia's own interface handles payments and content delivery, many creators want a custom-branded portal — a dashboard where students can see their enrolled courses, track progress, access bonus resources, or unlock community features based on their purchase status. Bolt.new lets you build that custom layer on top of Podia by connecting to Podia's REST API.

The Podia API is available on the Mover plan ($33/month) and above. It exposes endpoints for reading customer records, subscriptions, and product purchases. In Bolt, you prompt the AI to generate API routes that proxy requests to Podia using your API token, then build React components that display personalized dashboards for your students. The API token lives in a .env file during development and in your hosting platform's environment variables after deployment — it never touches client-side code.

If you're on Podia's free Starter plan, you don't have API access, but you can still integrate with Bolt using Podia's embed options: checkout buttons with a direct Podia checkout URL, or the JavaScript embed widget that renders a full Podia storefront inside your Bolt app. These approaches require no API token and work immediately in the Bolt preview. Podia webhooks — for triggering actions when someone enrolls or cancels — require a publicly accessible URL, so you'll need to deploy before testing that part of the flow.

## Before you start

- A Podia account — Mover plan ($33/month) or above for API access; free Starter plan works for embed-only integration
- Your Podia API token from Podia Dashboard → Settings → Integrations → API (Mover plan and above only)
- Your Podia store URL (e.g., yourname.podia.com) for embed links and widget configuration
- A Bolt.new project with a Next.js setup — request Next.js when starting the project for API route support
- A deployed URL on Netlify or Vercel for testing Podia webhooks in production

## Step-by-step guide

### 1. Set Up Your Podia API Token in the .env File

The Podia API uses token-based authentication. Every request to the Podia REST API must include an Authorization header with your API token. To find your token, go to your Podia Dashboard, click your account avatar in the top-right corner, select Settings, then navigate to the Integrations tab. You'll see an API section with your personal API token — copy this value. In your Bolt.new project, open the .env file in the file tree (or create one in the project root if it doesn't exist). Add the line PODIA_API_TOKEN=your_token_here. For a Next.js project, this is a server-side variable without the NEXT_PUBLIC_ prefix, which means it stays on the server and is never exposed to the browser. The Podia API base URL is https://developers.podia.com/v1 — all endpoints are prefixed with this. Keep in mind that API access is only available on the Mover plan and higher. If you're on the Starter plan, skip ahead to the embed step for the widget-based integration instead. Your .env file should also include NEXT_PUBLIC_PODIA_STORE_URL set to your Podia store URL so client components can link to your store without hardcoding the URL.

```
# .env
# Podia API token — available on Mover plan and above
# Found in: Podia Dashboard → Settings → Integrations → API
PODIA_API_TOKEN=your_podia_api_token_here

# Your Podia store URL (used for embed links and fallback CTAs)
NEXT_PUBLIC_PODIA_STORE_URL=https://yourname.podia.com
```

**Expected result:** The .env file contains your Podia API token and store URL. The token is not referenced in any client-side component — only in server-side API routes.

### 2. Prompt Bolt to Generate a Podia API Route

With the API token configured, prompt Bolt to create an API route that proxies requests to Podia's REST API. The route handles the authentication header and returns Podia data to your React components. Podia's API provides three main endpoints relevant to student portal use cases: GET /customers lists all customers with their email, name, and purchase history; GET /customers/{id} returns a specific customer record; GET /subscriptions lists active subscription records. Because CORS would block direct browser-to-Podia API calls, the API route pattern is essential — your React components call your own Next.js API route, which calls Podia from the server side using the token from process.env. Bolt will generate the route file, the TypeScript types for Podia responses, and a React hook or fetch function that your components can use. Review the generated code to confirm the Authorization header format matches Podia's requirement: Bearer your_token_here. Check the generated Podia response types too — the customer object contains fields like email, full_name, and products, where each product has a name, access_url, and created_at timestamp. The API route should accept an optional email query parameter so you can look up a specific customer without fetching your entire customer list.

```
// app/api/podia/customers/route.ts
import { NextRequest, NextResponse } from 'next/server';

export interface PodiaProduct {
  id: string;
  name: string;
  access_url: string;
  created_at: string;
}

export interface PodiaSubscription {
  id: string;
  status: 'active' | 'cancelled' | 'past_due';
  product_name: string;
  created_at: string;
}

export interface PodiaCustomer {
  id: string;
  email: string;
  full_name: string;
  products: PodiaProduct[];
  subscriptions: PodiaSubscription[];
}

export async function GET(request: NextRequest) {
  const apiToken = process.env.PODIA_API_TOKEN;

  if (!apiToken) {
    return NextResponse.json(
      { error: 'Podia API token not configured' },
      { status: 500 }
    );
  }

  const { searchParams } = new URL(request.url);
  const email = searchParams.get('email');

  try {
    const url = email
      ? `https://developers.podia.com/v1/customers?email=${encodeURIComponent(email)}`
      : 'https://developers.podia.com/v1/customers';

    const response = await fetch(url, {
      headers: {
        Authorization: `Bearer ${apiToken}`,
        'Content-Type': 'application/json',
      },
    });

    if (!response.ok) {
      const error = await response.text();
      return NextResponse.json(
        { error: `Podia API error: ${response.status}`, details: error },
        { status: response.status }
      );
    }

    const data = await response.json();
    return NextResponse.json(data);
  } catch (err) {
    console.error('Podia API fetch failed:', err);
    return NextResponse.json(
      { error: 'Failed to reach Podia API' },
      { status: 500 }
    );
  }
}
```

**Expected result:** Visiting /api/podia/customers in the browser returns a JSON array of Podia customers. Visiting /api/podia/customers?email=student@example.com returns the matching customer record or an empty array.

### 3. Build the Student Dashboard Component

With the API route in place, prompt Bolt to create the student-facing dashboard that fetches and displays a user's Podia courses. The dashboard flow: the student enters their email (or it's pulled from your app's auth system), the app calls /api/podia/customers?email=... to look up their Podia customer record, and the returned products array is rendered as a list of enrolled courses with access links. Each product in the Podia response includes a name, an access_url pointing directly to the Podia course player, and a created_at timestamp. The dashboard component should handle three states clearly: loading (while fetching from the API), empty (customer not found or no products purchased), and populated (showing the enrolled courses). If your Bolt app has authentication via NextAuth or Clerk, you can pre-fill the email from the session so students don't need to type it manually — this creates a seamless single sign-on experience where logging into your Bolt app automatically surfaces their Podia content. For richer course data like thumbnails or descriptions, you'll need to store that metadata in your own database since the Podia API focuses on transactional data and access rights rather than full course content metadata.

```
// app/dashboard/page.tsx
'use client';
import { useState } from 'react';
import type { PodiaCustomer } from '@/app/api/podia/customers/route';

export default function DashboardPage() {
  const [email, setEmail] = useState('');
  const [customer, setCustomer] = useState<PodiaCustomer | null>(null);
  const [loading, setLoading] = useState(false);
  const [notFound, setNotFound] = useState(false);

  async function handleLookup(e: React.FormEvent) {
    e.preventDefault();
    setLoading(true);
    setNotFound(false);
    setCustomer(null);

    const res = await fetch(`/api/podia/customers?email=${encodeURIComponent(email)}`);
    const data = await res.json();

    setLoading(false);

    const customers: PodiaCustomer[] = Array.isArray(data) ? data : (data.customers ?? []);
    const match = customers.find((c) => c.email.toLowerCase() === email.toLowerCase());

    if (match) {
      setCustomer(match);
    } else {
      setNotFound(true);
    }
  }

  return (
    <main className="max-w-2xl mx-auto p-8">
      <h1 className="text-2xl font-bold mb-6">My Courses</h1>
      <form onSubmit={handleLookup} className="flex gap-3 mb-8">
        <input
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          placeholder="Enter your email"
          required
          className="flex-1 border rounded-lg px-4 py-2"
        />
        <button
          type="submit"
          disabled={loading}
          className="bg-indigo-600 text-white px-6 py-2 rounded-lg hover:bg-indigo-700 disabled:opacity-50"
        >
          {loading ? 'Looking up...' : 'Find My Courses'}
        </button>
      </form>

      {notFound && (
        <div className="text-center py-8">
          <p className="text-gray-600 mb-4">No courses found for this email. Purchase a course to get started.</p>
          <a
            href={process.env.NEXT_PUBLIC_PODIA_STORE_URL}
            className="text-indigo-600 underline"
          >
            Browse courses on Podia
          </a>
        </div>
      )}

      {customer && (
        <div>
          <p className="text-gray-500 mb-4">Welcome back, {customer.full_name}!</p>
          {customer.products.length === 0 ? (
            <p className="text-gray-600">You have no enrolled courses yet.</p>
          ) : (
            <div className="grid gap-4">
              {customer.products.map((product) => (
                <div key={product.id} className="border rounded-xl p-5 flex justify-between items-center">
                  <div>
                    <h3 className="font-semibold">{product.name}</h3>
                    <p className="text-sm text-gray-500">
                      Enrolled on{' '}
                      {new Date(product.created_at).toLocaleDateString('en-US', {
                        year: 'numeric',
                        month: 'short',
                        day: 'numeric',
                      })}
                    </p>
                  </div>
                  <a
                    href={product.access_url}
                    target="_blank"
                    rel="noopener noreferrer"
                    className="bg-green-600 text-white px-4 py-2 rounded-lg hover:bg-green-700 text-sm"
                  >
                    Access Course
                  </a>
                </div>
              ))}
            </div>
          )}
        </div>
      )}
    </main>
  );
}
```

**Expected result:** The /dashboard page renders a lookup form. Entering a Podia customer's email displays their enrolled courses as cards with access links. Entering an unrecognized email shows the not-found message with a link to your Podia store.

### 4. Embed the Podia Widget for Free Plan Users

If you're on Podia's free Starter plan, you don't have access to the REST API — but you can still build a Bolt app that showcases and sells your Podia products using Podia's embed widget. Podia provides a JavaScript snippet that renders your entire storefront inline on any web page. The widget loads from Podia's CDN, displays your products, and handles the checkout flow entirely within Podia's hosted experience. To get the embed code, go to your Podia Dashboard → Storefront → Embed. In Bolt, prompt the AI to add a Script tag that loads the Podia embed JavaScript and a container div where the widget renders. Because the embed script comes from an external CDN, use Next.js's built-in Script component with strategy='lazyOnload' to avoid blocking page render. Individual product checkout links (direct URL buttons) don't require any JavaScript at all — you can link directly to yourname.podia.com/your-product-name/buy for a specific product's checkout page. These direct links work immediately in the Bolt preview since they're standard hyperlinks with no API calls required, and they function on the free Starter plan. Use direct links as your primary CTAs alongside the embed widget as a supplementary browsing experience.

```
// app/courses/page.tsx
import Script from 'next/script';

const PODIA_STORE_URL = process.env.NEXT_PUBLIC_PODIA_STORE_URL ?? 'https://yourname.podia.com';

// Direct checkout links — no API needed, works on all Podia plans including free
const featuredProducts = [
  { name: 'Beginner Course', checkoutUrl: `${PODIA_STORE_URL}/beginner-course/buy` },
  { name: 'Pro Masterclass', checkoutUrl: `${PODIA_STORE_URL}/pro-masterclass/buy` },
  { name: 'Monthly Membership', checkoutUrl: `${PODIA_STORE_URL}/membership/buy` },
];

export default function CoursesPage() {
  return (
    <main className="max-w-4xl mx-auto p-8">
      <h1 className="text-3xl font-bold mb-4">Our Courses</h1>

      {/* Direct checkout links — no JavaScript required, works with ad blockers */}
      <div className="grid grid-cols-3 gap-4 mb-12">
        {featuredProducts.map((product) => (
          <a
            key={product.name}
            href={product.checkoutUrl}
            className="block bg-indigo-600 text-white text-center px-4 py-3 rounded-xl font-medium hover:bg-indigo-700 transition"
          >
            Buy {product.name}
          </a>
        ))}
      </div>

      {/* Full Podia storefront embed */}
      <div id="podia-embed-container" style={{ minHeight: '600px' }}>
        <p className="text-gray-400 text-sm">Loading storefront...</p>
      </div>

      {/* Fallback for users with JavaScript disabled or ad blockers */}
      <p className="text-sm text-gray-500 mt-4">
        Having trouble viewing?{' '}
        <a href={PODIA_STORE_URL} className="text-indigo-600 underline">
          Visit our store directly
        </a>
      </p>

      {/* Podia embed script — lazyOnload avoids blocking page render */}
      <Script
        src="https://podia.com/embed.js"
        data-store={PODIA_STORE_URL}
        data-container="podia-embed-container"
        strategy="lazyOnload"
      />
    </main>
  );
}
```

**Expected result:** The /courses page shows direct checkout buttons for your key products and renders the Podia storefront widget below them. Clicking a checkout button opens the Podia checkout page. The embed widget loads the full product catalog after the initial page render.

### 5. Deploy and Register Podia Webhooks

Podia webhooks notify your Bolt app when enrollment events happen — a new customer purchases a course, a subscription renews, or a member cancels. During development in Bolt's WebContainer, webhooks cannot reach the browser-based runtime because there's no public URL for Podia to POST to. You must deploy first to test the complete webhook flow. Deploy your Bolt app to Netlify or Vercel: export your project from Bolt by connecting it to GitHub via the Git panel, then import the repo in your hosting platform. Add the PODIA_API_TOKEN environment variable in your hosting platform's environment settings (Netlify: Site configuration → Environment variables; Vercel: Project → Settings → Environment Variables) and redeploy. Once deployed, go to your Podia Dashboard → Settings → Integrations → Webhooks and add your endpoint URL in the format https://your-app.netlify.app/api/podia/webhook. Podia sends POST requests to this URL for events like new_customer, new_subscription, and subscription_cancelled. Your webhook handler should return a 200 status quickly to acknowledge receipt. Importantly, Podia does not sign webhook payloads with a cryptographic header (unlike Stripe's Stripe-Signature), so protect your endpoint by embedding a secret token in the URL path itself — register the webhook as /api/podia/webhook/your-secret-token and validate the token in the handler.

```
// app/api/podia/webhook/[token]/route.ts
import { NextRequest, NextResponse } from 'next/server';

interface PodiaWebhookEvent {
  event: 'new_customer' | 'new_subscription' | 'subscription_cancelled';
  customer?: {
    email: string;
    full_name: string;
  };
  subscription?: {
    id: string;
    product_name: string;
    status: string;
  };
}

export async function POST(
  request: NextRequest,
  { params }: { params: { token: string } }
) {
  // Validate secret token in URL path
  const expectedToken = process.env.PODIA_WEBHOOK_SECRET;
  if (!expectedToken || params.token !== expectedToken) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }

  let event: PodiaWebhookEvent;
  try {
    event = await request.json();
  } catch {
    return NextResponse.json({ error: 'Invalid JSON payload' }, { status: 400 });
  }

  console.log('Podia webhook received:', JSON.stringify(event, null, 2));

  switch (event.event) {
    case 'new_customer': {
      const { email, full_name } = event.customer ?? {};
      console.log('New Podia customer:', email, full_name);
      // TODO: add to email list, create user record, trigger welcome email
      break;
    }
    case 'new_subscription': {
      const { email } = event.customer ?? {};
      const planName = event.subscription?.product_name;
      console.log(`New subscription: ${email} subscribed to ${planName}`);
      // TODO: update subscription status in your database
      break;
    }
    case 'subscription_cancelled': {
      const { email } = event.customer ?? {};
      console.log('Subscription cancelled for:', email);
      // TODO: revoke premium access in your database
      break;
    }
    default:
      console.log('Unhandled Podia event type:', (event as { event: string }).event);
  }

  return NextResponse.json({ received: true });
}
```

**Expected result:** After deploying and registering the webhook in Podia Dashboard, purchasing a test product sends a POST to your deployed endpoint. Your server logs show the event payload and the corresponding handler branch executes. Requests with an incorrect token return 401.

## Best practices

- Keep PODIA_API_TOKEN server-side only — never use the NEXT_PUBLIC_ prefix or reference it in any client component or browser-accessible code
- Cache Podia API customer data in your own database rather than querying Podia on every page load — the API has rate limits and a local database lookup is significantly faster for the end user
- Use direct Podia checkout links (yourname.podia.com/product-slug/buy) as your primary purchase CTAs — they work on all plans including free and are not affected by ad blockers
- Protect your webhook endpoint by embedding a secret token in the URL path since Podia does not provide cryptographic payload signing like Stripe does
- Test the full student dashboard and checkout flows on your deployed URL before announcing to your audience — Podia webhooks only work on publicly accessible URLs, not in Bolt's WebContainer preview
- Normalize customer emails to lowercase before comparison — Podia may store emails with capitalization that differs from how users type their email in your dashboard form
- Always show a clear fallback CTA linking to your Podia store when a customer is not found — never show a generic error message to users who may have purchased under a different email

## Use cases

### Custom Student Dashboard Showing Enrolled Courses

Build a personalized dashboard where logged-in students can see all the Podia courses they've purchased, along with links to access each course. The dashboard fetches the customer record from Podia's API using the student's email, then displays their active enrollments with course names and direct access links.

Prompt example:

```
Build a student dashboard page at /dashboard. It should fetch the current user's Podia customer record by email using the Podia API (token stored in PODIA_API_TOKEN). Display all their active course enrollments with course name, enrollment date, and a 'Go to Course' button linking to the Podia course URL. If the API returns no customer record, show a 'No purchases found' message with a link to the Podia store.
```

### Subscription-Gated Bonus Content

Create a members-only section in your Bolt app that becomes accessible only when a visitor has an active Podia membership subscription. When someone visits the gated page, the app checks their email against the Podia API to verify they have an active subscription before rendering the content.

Prompt example:

```
Create a /members page that shows exclusive bonus content only to active Podia subscribers. When a user enters their email and clicks 'Access Members Area', call the Podia API to check if they have an active subscription. If they do, render the members content. If not, show a CTA button linking to the Podia membership checkout page. Store PODIA_API_TOKEN in the .env file.
```

### Embedded Podia Storefront for Free Plan Users

For creators on Podia's free Starter plan without API access, embed the Podia JavaScript checkout widget directly in a Bolt app landing page. Visitors can browse and purchase courses without leaving your custom-branded site, and no API token is required.

Prompt example:

```
Add a Podia storefront embed to my /courses page. Use Next.js's Script component with strategy='lazyOnload' to load the Podia embed JavaScript from Podia's CDN and render it inside a div with id='podia-embed-container'. Below the embed, add a fallback link to my Podia store URL for users with JavaScript disabled. Also add direct checkout buttons at the top linking to specific Podia product URLs.
```

## Troubleshooting

### Every request to the Podia API returns 401 Unauthorized

Cause: The PODIA_API_TOKEN environment variable is missing, the Authorization header format is incorrect (Bearer prefix required), or your Podia plan does not include API access — the API is only available on Mover plan and above.

Solution: Check the .env file contains PODIA_API_TOKEN without quotes around the token value. Verify the API route sends the header as 'Authorization: Bearer your_token_here' — the word Bearer followed by a space is required. Confirm your Podia plan: if you're on the free Starter plan, the API token section will not appear in Podia Dashboard → Settings → Integrations. Upgrade to Mover plan or use the embed widget approach instead.

```
// Correct Authorization header format
headers: {
  Authorization: `Bearer ${process.env.PODIA_API_TOKEN}`,
  'Content-Type': 'application/json',
}
```

### The student email lookup returns no results even for a customer who definitely made a purchase

Cause: The email comparison is case-sensitive and the Podia customer record may be stored with different letter casing, or Podia is returning paginated results and the matching customer is on a subsequent page beyond the first.

Solution: Normalize both the input email and the Podia customer email to lowercase before comparing. For stores with more than a page of customers, check if the response includes a next_page_url field and fetch subsequent pages until either a match is found or all pages are exhausted. Consider syncing customer records to your own database on a schedule rather than querying Podia live on every lookup.

```
// Case-insensitive email matching
const match = customers.find(
  (c) => c.email.toLowerCase() === inputEmail.toLowerCase()
);
```

### Podia webhook POST requests never arrive at the deployed endpoint

Cause: The webhook URL registered in Podia Dashboard points to the Bolt WebContainer preview URL instead of the deployed Netlify or Vercel URL, or the endpoint was registered before the app was deployed.

Solution: Go to Podia Dashboard → Settings → Integrations → Webhooks and verify the endpoint URL is your deployed domain (e.g., https://your-app.netlify.app/api/podia/webhook/your-secret-token), not a bolt.new or localhost URL. Use the 'Send test event' button in Podia's webhook settings to manually trigger a test delivery and check the response status. Ensure the app was redeployed after adding PODIA_WEBHOOK_SECRET to the hosting platform's environment variables.

### The Podia embed widget shows a blank area that never loads the storefront

Cause: The Script component's data-store attribute contains an incorrect Podia store URL, or an ad blocker in the browser is preventing the Podia embed script from loading.

Solution: Verify NEXT_PUBLIC_PODIA_STORE_URL in .env is set to the exact Podia store URL in the format https://yourname.podia.com with no trailing slash. Open the browser console and look for script load errors or 404 responses on the Podia CDN URL. As a reliable fallback, replace or supplement the embed widget with direct product checkout links (yourname.podia.com/product-slug/buy) which work regardless of ad blockers.

## Frequently asked questions

### Does Bolt.new work with Podia?

Yes. You can connect Bolt.new to Podia through the Podia REST API (available on Mover plan and above) to fetch customer and subscription data and build custom student dashboards. For free Starter plan users, you can embed the Podia storefront JavaScript widget or link directly to Podia checkout URLs — both approaches require no API token and work immediately in the Bolt preview.

### Can I use the Podia API on the free Starter plan?

No. Podia's REST API is only available on the Mover plan ($33/month) and above. On the free Starter plan, use the embed widget integration instead — load Podia's JavaScript embed script in your Bolt app to display your full storefront inline, or link directly to specific product checkout URLs at yourname.podia.com/product-slug/buy without any API calls required.

### Can I test Podia webhook events in Bolt's WebContainer preview?

No. Podia webhooks require a publicly accessible URL to send POST requests to, and Bolt's WebContainer development environment runs in the browser without a public URL. You must deploy your Bolt app to Netlify or Vercel first, then register the deployed URL as a webhook endpoint in Podia Dashboard → Settings → Integrations → Webhooks. You can write and review the webhook handler code in Bolt, but real webhook delivery only works after deployment.

### How do I gate content in my Bolt app based on a Podia course purchase?

Build a lookup flow that checks the visitor's email against the Podia /customers API endpoint. If the API response includes the specific course in their products array, render the gated content. If not, show a CTA linking to the Podia checkout page for that course. For a smoother repeat-visit experience, store verified customer emails and their purchased product IDs in your own database after the first successful Podia API lookup.

### How do I deploy my Bolt app with Podia integration to Netlify?

Connect your Bolt project to GitHub via Bolt's Git panel, then import that repo in Netlify. Set the build command to npm run build and publish directory to .next for a Next.js project. Before deploying, add PODIA_API_TOKEN and NEXT_PUBLIC_PODIA_STORE_URL in Netlify's Site configuration → Environment variables and trigger a new deploy. After the first successful deployment, register your Netlify URL as a Podia webhook endpoint if you're using webhook-driven enrollment features.

### How is Podia different from Teachable for building a custom Bolt.new integration?

Podia is an all-in-one platform covering courses, digital downloads, and memberships in a single product, while Teachable focuses specifically on courses with richer student analytics and a more permissive API. Teachable's API is accessible on lower-tier plans, whereas Podia's API requires the Mover plan. If detailed per-student progress tracking via API is important to your Bolt app, Teachable offers more data points to work with.

---

Source: https://www.rapidevelopers.com/bolt-ai-integrations/podia
© RapidDev — https://www.rapidevelopers.com/bolt-ai-integrations/podia
