# How to Integrate Bolt.new with AWeber

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

## TL;DR

Integrate AWeber with Bolt.new using their REST API v1 and OAuth 2.0 authentication. For personal single-account integrations, generate an access token directly from the AWeber API management page. For multi-user apps, implement the OAuth authorization code flow. Store the access token in a server-side environment variable and call AWeber's API from a Next.js API route. OAuth callbacks require a deployed Netlify URL.

## Add AWeber Email Marketing to Your Bolt.new App

AWeber has been an email marketing platform since 1998 — its longevity means it's deeply integrated into the marketing stacks of thousands of small businesses and content creators. Adding AWeber integration to a Bolt app lets you capture email subscribers directly from your app and feed them into AWeber's automated email sequences, broadcasts, and campaign analytics. Whether you're building a SaaS landing page, a content portal, or a product waitlist, a Bolt-to-AWeber integration keeps your marketing list synchronized without any manual export and import.

The integration uses AWeber's OAuth 2.0 API, which requires creating a developer app in AWeber's developer portal. AWeber provides both a web server OAuth flow (for user-facing apps where subscribers authenticate with their own AWeber accounts) and a way to use your own account credentials for service-level integrations. For most Bolt use cases — capturing form submissions from your app's visitors into your own AWeber list — you'll use your own account's access token, obtained through the OAuth flow for your AWeber account.

AWeber's API is well-documented at developer.aweber.com and follows standard REST conventions. The platform's free tier supports up to 500 subscribers with full API access, making it an accessible starting point for indie developers. The API covers the full subscriber lifecycle: adding new subscribers, tagging them based on actions, unsubscribing on request, and fetching analytics about email performance. Pair it with a lead magnet form in your Bolt app and AWeber handles all the follow-up automation.

## Before you start

- An AWeber account (free tier available at aweber.com for up to 500 subscribers)
- An AWeber developer app created at labs.aweber.com/apps (or developer.aweber.com) with OAuth 2.0 credentials
- Your AWeber Account ID (visible in AWeber Dashboard → Account → Account Settings)
- Your AWeber List ID (visible in AWeber Dashboard → List Options → List Settings)
- A deployed URL on Netlify or Bolt Cloud for registering the OAuth callback redirect URI

## Step-by-step guide

### 1. Create an AWeber Developer App and Get OAuth Credentials

AWeber's REST API uses OAuth 2.0 for all authentication. Start by creating a developer app at labs.aweber.com/apps (you'll need to be logged in to your AWeber account). Click 'Create an App', fill in the app name and description, and enter your redirect URL. For development, you can use a placeholder redirect URL like https://your-app.netlify.app/api/aweber/callback — update it after deploying. Once the app is created, you'll receive a Client ID and Client Secret. Store these in your .env file as AWEBER_CLIENT_ID and AWEBER_CLIENT_SECRET. AWeber uses the Authorization Code OAuth 2.0 flow — your app redirects users to AWeber's authorization page, AWeber sends an authorization code to your callback URL, and your callback exchanges it for an access token and refresh token. For integrations where you're connecting your own AWeber account (not your users' accounts), you can manually complete the OAuth flow once and save the access token as AWEBER_ACCESS_TOKEN — this token can be refreshed automatically. AWeber's access tokens expire after one hour; refresh tokens are valid for much longer. Implement token refresh logic in your API routes: if an API call returns 401, use the refresh token to obtain a new access token before retrying. Your AWeber Account ID is visible in the AWeber Dashboard URL when logged in: app.aweber.com/accounts/{account_id}/. Your List ID is found in List Options → Settings for each list.

```
// lib/aweber.ts
interface TokenCache {
  accessToken: string;
  expiresAt: number;
}

let tokenCache: TokenCache | null = null;

async function getAccessToken(): Promise<string> {
  // Return cached token if still valid (with 5 min buffer)
  if (tokenCache && Date.now() < tokenCache.expiresAt - 300000) {
    return tokenCache.accessToken;
  }

  // Refresh the token
  const params = new URLSearchParams({
    grant_type: 'refresh_token',
    refresh_token: process.env.AWEBER_REFRESH_TOKEN!,
    client_id: process.env.AWEBER_CLIENT_ID!,
    client_secret: process.env.AWEBER_CLIENT_SECRET!,
  });

  const res = await fetch('https://auth.aweber.com/oauth2/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: params.toString(),
  });

  if (!res.ok) {
    throw new Error(`AWeber token refresh failed: ${res.statusText}`);
  }

  const data = await res.json();
  tokenCache = {
    accessToken: data.access_token,
    expiresAt: Date.now() + data.expires_in * 1000,
  };

  return data.access_token;
}

export async function aweberRequest<T>(
  path: string,
  method: string = 'GET',
  body?: Record<string, unknown>
): Promise<T> {
  const token = await getAccessToken();
  const res = await fetch(`https://api.aweber.com/1.0/${path}`, {
    method,
    headers: {
      Authorization: `Bearer ${token}`,
      'Content-Type': 'application/json',
      Accept: 'application/json',
    },
    ...(body ? { body: JSON.stringify(body) } : {}),
  });

  if (!res.ok) {
    throw new Error(`AWeber API error ${res.status}: ${await res.text()}`);
  }

  return res.json();
}

export async function addSubscriber(email: string, name?: string, listId?: string) {
  const accountId = process.env.AWEBER_ACCOUNT_ID;
  const targetListId = listId || process.env.AWEBER_LIST_ID;
  return aweberRequest(
    `accounts/${accountId}/lists/${targetListId}/subscribers`,
    'POST',
    { email, name: name || '', ad_tracking: 'bolt-app', status: 'subscribed' }
  );
}
```

**Expected result:** The AWeber authentication utility is set up with automatic token refresh. With valid credentials in .env, the aweberRequest function successfully calls the AWeber API.

### 2. Build the Email Signup Form and Subscriber Route

With AWeber authentication ready, build the email capture form and the API route that handles subscriptions. The form should be simple but effective — typically an email field, optional name field, and a clear call-to-action button. Add client-side validation for email format before submitting to the API. The API route calls AWeber's subscriber creation endpoint, which either creates a new subscriber or updates an existing one if the email is already in the list. AWeber handles the double opt-in confirmation email automatically based on your list's settings — if double opt-in is enabled (recommended for list quality), the subscriber receives a confirmation email and is only added to the active subscriber count after clicking the link. The API route should handle the common error cases gracefully: duplicate email (AWeber returns an error when the email already exists with the same status — handle this as a success since the subscriber is already on the list), invalid email format, and API authentication errors. Add a rate limiting consideration: if your signup form is on a high-traffic page, implement basic request rate limiting on the API route to prevent abuse. For the form UI, a common pattern is a simple inline form in the hero section (email input + button on the same line for desktop, stacked for mobile). After a successful subscription, update the button to show 'Subscribed!' with a checkmark and disable the form to prevent duplicate submissions. Track signup sources using AWeber's ad_tracking field — set it to a string identifying where the subscriber signed up (e.g., 'blog-header-form', 'homepage-footer', 'exit-intent-popup') so you can see which forms drive the most subscriptions in AWeber's analytics.

```
// app/api/aweber/subscribe/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { addSubscriber } from '@/lib/aweber';

export async function POST(request: NextRequest) {
  const { email, first_name, source } = await request.json();

  // Email validation
  if (!email || !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)) {
    return NextResponse.json({ error: 'Please enter a valid email address' }, { status: 400 });
  }

  try {
    await addSubscriber(
      email,
      first_name || '',
      process.env.AWEBER_LIST_ID
    );

    return NextResponse.json({ success: true });
  } catch (error) {
    const errorMessage = String(error);

    // Handle 'already subscribed' as success
    if (errorMessage.includes('already subscribed') || errorMessage.includes('duplicate')) {
      return NextResponse.json({ success: true });
    }

    console.error('AWeber subscribe error:', error);
    return NextResponse.json(
      { error: 'Failed to subscribe. Please try again.' },
      { status: 500 }
    );
  }
}
```

**Expected result:** Submitting the signup form adds the email to your AWeber list. In AWeber Dashboard → Subscribers, the new subscriber appears. If double opt-in is enabled, they receive a confirmation email before being activated.

### 3. Implement the OAuth Callback for User-Facing Authentication

If you're building an app where users connect their own AWeber accounts (rather than using your own account credentials), you need to implement the full OAuth 2.0 web server flow with a browser redirect. This requires a deployed Bolt app with a stable callback URL — the WebContainer preview's dynamic URL cannot be registered as an OAuth redirect URI in AWeber's developer portal. Deploy to Netlify or Bolt Cloud first to get a stable URL (e.g., https://your-app.netlify.app), then update the redirect URL in your AWeber developer app settings to https://your-app.netlify.app/api/aweber/callback. The OAuth flow has three steps: your app redirects the user to AWeber's authorization URL (https://auth.aweber.com/oauth2/authorize) with your Client ID, requested scopes, and callback URL as query parameters. The user logs in to AWeber and grants your app permission to access their account. AWeber redirects back to your callback URL with an authorization code. Your callback exchanges the code for an access token and refresh token via POST to https://auth.aweber.com/oauth2/token. Store both tokens per user (in Supabase, associated with their account in your app). Include a CSRF protection state parameter in the authorization URL and verify it in the callback to prevent cross-site request forgery. AWeber's required scopes for list management are: subscriber.read, subscriber.write, list.read. Request the minimum scopes needed for your use case — users see the requested permissions on the authorization screen.

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

export async function GET(request: NextRequest) {
  const { searchParams } = new URL(request.url);
  const code = searchParams.get('code');
  const state = searchParams.get('state');
  const stateCookie = request.cookies.get('aweber_oauth_state')?.value;

  // CSRF protection: verify state matches
  if (!state || state !== stateCookie) {
    return NextResponse.redirect(new URL('/settings?error=invalid_state', request.url));
  }

  if (!code) {
    return NextResponse.redirect(new URL('/settings?error=no_code', request.url));
  }

  const params = new URLSearchParams({
    grant_type: 'authorization_code',
    code,
    client_id: process.env.AWEBER_CLIENT_ID!,
    client_secret: process.env.AWEBER_CLIENT_SECRET!,
    redirect_uri: `${process.env.APP_URL}/api/aweber/callback`,
  });

  const tokenRes = await fetch('https://auth.aweber.com/oauth2/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: params.toString(),
  });

  if (!tokenRes.ok) {
    return NextResponse.redirect(new URL('/settings?error=token_failed', request.url));
  }

  const { access_token, refresh_token } = await tokenRes.json();
  // Store tokens in your database or secure session

  const response = NextResponse.redirect(new URL('/dashboard', request.url));
  response.cookies.delete('aweber_oauth_state');
  return response;
}
```

**Expected result:** After deployment, clicking 'Connect AWeber' redirects to AWeber's login page. After authorization, users are redirected back to the dashboard with their AWeber account connected.

### 4. Deploy and Build the Analytics Dashboard

Deploy your Bolt app to access AWeber's analytics endpoints and complete the OAuth setup with your deployed callback URL. Deploy using Bolt's Publish button (deploys to Bolt Cloud) or push to GitHub and connect to Netlify. Set all environment variables on the hosting platform: AWEBER_CLIENT_ID, AWEBER_CLIENT_SECRET, AWEBER_ACCESS_TOKEN, AWEBER_REFRESH_TOKEN, AWEBER_ACCOUNT_ID, and AWEBER_LIST_ID. Update your AWeber developer app's redirect URL to use your deployed domain. Build the analytics dashboard to show list performance: subscriber count over time, recent campaign metrics (open rate, click rate, bounces), and subscriber acquisition by source (using the ad_tracking field data). AWeber's API provides list analytics at /accounts/{account_id}/lists/{list_id} and broadcast (campaign) metrics at /accounts/{account_id}/lists/{list_id}/messages. The broadcast metrics endpoint returns per-email stats: total sent, opens, clicks, unsubscribes, bounces. Display these in a table or card format. For subscriber growth trends, you may need to store historical counts in Supabase since AWeber's API returns current subscriber counts, not historical time series data — record the count daily via a scheduled function or simply on each API call. Remember: AWeber API calls are outbound requests from your server-side routes and work in the WebContainer preview, but the OAuth callback (browser redirect) requires a deployed URL. You can build and test the analytics dashboard in the preview using a static AWEBER_ACCESS_TOKEN, then update the deployed app to use the OAuth-refreshed tokens.

```
// app/api/aweber/lists/route.ts
import { NextResponse } from 'next/server';
import { aweberRequest } from '@/lib/aweber';

export async function GET() {
  const accountId = process.env.AWEBER_ACCOUNT_ID;

  try {
    const data = await aweberRequest<{ entries: unknown[] }>(
      `accounts/${accountId}/lists`
    );
    return NextResponse.json(data);
  } catch (error) {
    console.error('AWeber lists fetch error:', error);
    return NextResponse.json({ error: 'Failed to fetch lists' }, { status: 500 });
  }
}

// app/api/aweber/campaigns/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { aweberRequest } from '@/lib/aweber';

export async function GET(request: NextRequest) {
  const { searchParams } = new URL(request.url);
  const listId = searchParams.get('list_id') || process.env.AWEBER_LIST_ID;
  const accountId = process.env.AWEBER_ACCOUNT_ID;

  try {
    const data = await aweberRequest(
      `accounts/${accountId}/lists/${listId}/messages?ws.size=10&ws.start=0`
    );
    return NextResponse.json(data);
  } catch (error) {
    return NextResponse.json({ error: 'Failed to fetch campaigns' }, { status: 500 });
  }
}
```

**Expected result:** The analytics dashboard shows subscriber counts per list and campaign performance metrics. The open rate color coding quickly identifies which campaigns performed above or below industry average.

## Best practices

- Implement automatic token refresh using the refresh token — AWeber access tokens expire after 1 hour and manual rotation is impractical for production apps
- Treat 'already subscribed' AWeber errors as success responses — this provides a smooth user experience when visitors submit the form multiple times
- Use the ad_tracking field to label where each subscriber joined (e.g., 'blog-header', 'exit-popup', 'product-launch') — AWeber analytics show subscription source, helping you optimize form placement
- Never put AWeber OAuth tokens or client secrets in client-side code — always proxy AWeber API calls through server-side routes
- Test the AWeber subscription API route in Bolt's WebContainer preview during development — the outbound API calls work fine without deploying
- Register the OAuth callback URL with your deployed domain before testing user-facing authentication — the WebContainer's dynamic preview URL cannot be registered as a stable redirect URI
- Store subscriber tags in your own database alongside AWeber tags — having a local record makes it easier to re-tag subscribers if you ever switch email platforms

## Use cases

### Email Capture Form with List Subscription

Add a newsletter signup form to your Bolt landing page that collects visitor emails and subscribes them to a specific AWeber list. On submission, the app calls an API route that authenticates with AWeber and creates or updates a subscriber profile — triggering any welcome email sequences configured in AWeber.

Prompt example:

```
Create a newsletter signup form for my Bolt landing page. The form has an email field, optional first name field, and a Subscribe button. On submit, call POST /api/aweber/subscribe with { email, first_name }. The API route should POST to https://api.aweber.com/1.0/accounts/{AWEBER_ACCOUNT_ID}/lists/{AWEBER_LIST_ID}/subscribers with Authorization: Bearer ${AWEBER_ACCESS_TOKEN}. Use body: { email, name: first_name, ad_tracking: 'bolt-app', status: 'subscribed' }. Show a success message 'Check your inbox for confirmation!' on success. Add AWEBER_ACCESS_TOKEN, AWEBER_ACCOUNT_ID, AWEBER_LIST_ID to .env as placeholders.
```

### Subscriber Tagging Based on In-App Actions

Tag AWeber subscribers based on actions they take in your Bolt app — purchased a plan, completed onboarding, used a specific feature. Tags trigger targeted email sequences in AWeber, enabling behavior-based email automation directly from your app's events.

Prompt example:

```
Build a subscriber tagging integration with AWeber. When a user in my Bolt app completes a specific action (e.g., upgrades to Pro, downloads a resource, completes onboarding), call POST /api/aweber/tag with { email, tag }. The API route should: 1) Find the subscriber by email using GET https://api.aweber.com/1.0/accounts/{account_id}/lists/{list_id}/subscribers?ws.op=find&email={email}. 2) Add the tag using PATCH on the subscriber URL with { tags: { add: [tag] } }. Create tag constants: 'upgraded_to_pro', 'completed_onboarding', 'downloaded_guide'. Handle the case where the subscriber doesn't exist yet (subscribe them first, then tag).
```

### Campaign Analytics Dashboard

Build an internal analytics dashboard in Bolt that shows AWeber campaign performance — subscriber count per list, recent campaign open rates, click rates, and unsubscribe trends. Gives your marketing team quick access to key metrics without opening AWeber's full interface.

Prompt example:

```
Build an AWeber analytics dashboard. Create API routes that fetch: GET /api/aweber/lists — all email lists with subscriber counts using GET https://api.aweber.com/1.0/accounts/{account_id}/lists. GET /api/aweber/campaigns — recent 10 broadcasts with subject, send_date, total_sent, opens_total, clicks_total from the messages API. Display as: a list of lists with subscriber counts and growth badges, and a table of recent campaigns with open rate (opens_total/total_sent as percentage) and click rate. Add a Recharts bar chart showing open rates across the last 5 campaigns. Use AWEBER_ACCESS_TOKEN for auth.
```

## Troubleshooting

### AWeber API returns 401 Unauthorized after the initial setup works

Cause: AWeber access tokens expire after one hour. Without token refresh logic, requests fail after the initial token expires.

Solution: Implement the token refresh logic shown in lib/aweber.ts — before each API call, check if the token is within 5 minutes of expiry and refresh using the refresh token. Store the new access token in memory (module-level cache) so it persists across requests within the same server process.

```
// Check expiry and refresh before API calls
if (!tokenCache || Date.now() > tokenCache.expiresAt - 300000) {
  // Refresh token using POST to https://auth.aweber.com/oauth2/token
  // with grant_type=refresh_token
}
```

### Subscriber creation fails with 'Subscriber already subscribed to this list'

Cause: AWeber raises an error when you attempt to subscribe an email that's already on the list with subscribed status.

Solution: Treat this error as success — the subscriber is already on the list, which is the desired state. Catch the error in your API route and check if the message contains 'already subscribed' before returning an error to the user.

```
// Handle 'already subscribed' as success
catch (error) {
  const msg = String(error);
  if (msg.includes('already subscribed') || msg.includes('duplicate')) {
    return NextResponse.json({ success: true }); // Already subscribed is fine
  }
  throw error; // Re-throw other errors
}
```

### OAuth callback redirect URL mismatch error

Cause: The redirect_uri in the authorization URL doesn't exactly match the URL registered in your AWeber developer app, or you're testing with the WebContainer preview URL instead of a deployed URL.

Solution: The redirect URI must match exactly (including https vs http, trailing slashes, and path). Update your AWeber developer app's redirect URL to match your deployed domain. Remember that Bolt's WebContainer preview URL is dynamic and cannot be used as a stable OAuth redirect URI — deploy to Netlify or Bolt Cloud first.

## Frequently asked questions

### Can I use AWeber's API in Bolt's WebContainer preview?

Yes — outbound API calls to AWeber's REST API work fine in Bolt's WebContainer preview. You can test subscriber creation, list fetching, and campaign analytics during development using credentials stored in your .env file. The limitation is OAuth callback flows: if you need users to authorize your app through AWeber's login page, the callback URL must be a deployed, stable URL — not the WebContainer's dynamic preview URL.

### Is AWeber free to use for API integrations?

AWeber's free plan supports up to 500 subscribers and includes full REST API access — there's no separate charge for using the API. Creating a developer app and obtaining OAuth credentials is also free. Paid plans start at $12.50/month for larger subscriber lists. The developer app creation at labs.aweber.com/apps requires an active AWeber account (free tier works).

### Does AWeber support double opt-in confirmation emails?

Yes — AWeber has configurable double opt-in (DOI) settings per list. When DOI is enabled, subscribers added via the API receive a confirmation email and are only activated after clicking the confirmation link. The API creates the subscriber in 'unconfirmed' status until they confirm. This is the recommended setting for maintaining list quality and reducing spam complaints.

### How do I get my AWeber Account ID and List ID?

Your Account ID appears in the URL when logged in to AWeber: app.aweber.com/accounts/{account_id}/. Your List ID is found in the AWeber Dashboard → List Options → List Settings → the URL contains the list ID. You can also fetch both via the API: GET https://api.aweber.com/1.0/accounts returns your account ID, and GET /1.0/accounts/{account_id}/lists returns all lists with their IDs.

### Can I subscribe users without them receiving a confirmation email?

Yes — when creating a subscriber via the API, set status: 'subscribed' with the ad_tracking field. If your list has double opt-in disabled, subscribers are added as active immediately without a confirmation email. If double opt-in is enabled at the list level, the confirmation email sends regardless of the API status parameter. Check your list's opt-in settings in AWeber → List Options → Confirmation Message.

---

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