To use Bandwidth with Bolt.new, prompt Bolt to install @bandwidth/sdk and create API routes that send SMS/MMS and make voice calls using your Bandwidth Account ID, API token, and API secret from the .env file. Outbound messaging API calls work in Bolt's WebContainer during development. Deploy to Netlify or Vercel before registering Bandwidth webhook URLs for inbound message and call event delivery — webhooks cannot reach the WebContainer.
Build High-Volume Messaging and Voice Features in Bolt.new with Bandwidth
Bandwidth stands apart from other communications APIs because they own and operate their own national Tier 1 network in the United States and Canada. While Twilio, Vonage, and Plivo resell capacity they lease from carriers, Bandwidth is the carrier — they directly connect to the public switched telephone network. This ownership translates to practical benefits for applications: better SMS deliverability for US numbers, direct control over number provisioning, competitive pricing at scale, and genuine carrier-grade reliability with a 99.999% uptime SLA.
For developers building applications in Bolt.new, Bandwidth provides two core APIs: the Messaging V2 API for SMS and MMS, and the Voice API with BXML (Bandwidth Extensible Markup Language) for call control. The Messaging V2 API is HTTP-based — you POST to an endpoint with your message parameters and Bandwidth delivers the message across their network. BXML is Bandwidth's equivalent to TwiML: an XML-based language that controls what happens during a voice call (play audio, record, gather digits, transfer to another number). Your Bolt app serves a URL that returns BXML when Bandwidth requests call instructions during an active call.
The integration architecture for Bolt follows the standard API-plus-webhook pattern. Outbound operations (sending a message, initiating a call) work in Bolt's WebContainer during development because they are outbound HTTP calls from your API route to Bandwidth's servers. Inbound operations (receiving a message, handling call events) require a publicly accessible URL where Bandwidth can POST event data — this is a fundamental WebContainer limitation. The WebContainer has no public URL, so incoming webhooks cannot be received during development. Deploy to Netlify or Vercel before testing the two-way communication features, then register your deployed webhook URLs in the Bandwidth Dashboard.
Integration method
Bolt.new integrates with Bandwidth by generating API routes that use the @bandwidth/sdk npm package to send messages and manage voice calls. Your Bandwidth Account ID, API token, and API secret live in the .env file. Outbound messaging works immediately in Bolt's WebContainer during development. Inbound message webhooks and call event callbacks require a deployed URL — the WebContainer cannot receive incoming HTTP connections — so deploy to Netlify or Vercel before testing the full two-way communication flow.
Prerequisites
- A Bandwidth account at bandwidth.com with a provisioned US or Canadian phone number
- Your Bandwidth Account ID, API token, and API secret from the Bandwidth Dashboard → API Credentials
- A Bolt.new project configured with Next.js (recommended for API routes) or Vite with a serverless function deployment target
- A provisioned Bandwidth phone number configured for messaging and/or voice in your Bandwidth account
- A deployed URL on Netlify or Vercel for registering Bandwidth webhook URLs for inbound messages and call events
Step-by-step guide
Get Your Bandwidth API Credentials and Phone Number
Get Your Bandwidth API Credentials and Phone Number
Bandwidth uses three credentials for API authentication: your Account ID, an API token, and an API secret. All three are required for every API call. Log in to the Bandwidth Dashboard at dashboard.bandwidth.com. Your Account ID (also called a subaccount ID) is displayed in the top navigation bar and in the URL of your account pages — it is a 7-10 digit number. Navigate to API Credentials (in the left sidebar under Settings or Security) to find your API token and API secret. The API token is a long alphanumeric string and the API secret is its paired secret. If you have not generated API credentials before, click the option to create new credentials. Both the token and secret are shown once when created — save them immediately to your password manager because the secret cannot be retrieved later. Next, ensure you have a phone number provisioned in your account. Navigate to Numbers → My Numbers to see your available numbers. If you do not have one, you can purchase a number through the Bandwidth Dashboard or use a trial number if you are on a trial account. Note the phone number in E.164 format (+1XXXXXXXXXX) — this is your from number for outbound messages and calls. Also note the messaging application ID and voice application ID if you have configured applications in the Dashboard, as some SDK methods require the application ID rather than just the from number.
Pro tip: Bandwidth uses multiple levels of account hierarchy: Account → Subaccount → Location. The Account ID you use for API calls is typically your main account number displayed in the dashboard header, not a subaccount ID.
Expected result: You have your Bandwidth Account ID, API token, API secret, and phone number ready to add to your .env file.
Add Credentials to .env and Install the Bandwidth SDK
Add Credentials to .env and Install the Bandwidth SDK
Add all Bandwidth credentials to your Bolt project's .env file. Bandwidth requires four environment variables: BANDWIDTH_ACCOUNT_ID (your numeric account ID), BANDWIDTH_API_TOKEN (the token), BANDWIDTH_API_SECRET (the secret), and BANDWIDTH_FROM_NUMBER (your provisioned E.164 phone number). Optionally add BANDWIDTH_MESSAGING_APP_ID if your messaging setup uses application IDs. These are all server-side credentials — none of them get the VITE_ or NEXT_PUBLIC_ prefix. Prompt Bolt to install the @bandwidth/sdk npm package, which is Bandwidth's official TypeScript/JavaScript SDK. The SDK provides typed classes for creating message requests, managing calls, and generating BXML. After installation, verify the package appears in package.json. The SDK uses Basic Authentication under the hood — it combines your API token and secret into a Base64-encoded Authorization header for each request. You provide the token and secret when constructing the SDK client and the authentication is handled automatically. The Bandwidth SDK supports both CommonJS and ES modules, so it works in both Vite and Next.js projects without additional configuration. Prompt Bolt to create a lib/bandwidth.ts utility that initializes and exports the SDK client, similar to how you would export a Supabase or Stripe client.
Install @bandwidth/sdk npm package. Add BANDWIDTH_ACCOUNT_ID, BANDWIDTH_API_TOKEN, BANDWIDTH_API_SECRET, BANDWIDTH_FROM_NUMBER, and BANDWIDTH_MESSAGING_APP_ID to .env with placeholder values. Create lib/bandwidth.ts that imports Client and MessagesApi from @bandwidth/sdk, initializes the client with credentials from process.env, and exports a configured messagesApi instance. The client should use the HTTP basic auth with API token and secret.
Paste this in Bolt.new chat
1// lib/bandwidth.ts2import { Client, MessagesApi, VoiceApi } from '@bandwidth/sdk';34const client = new Client({5 basicAuthUserName: process.env.BANDWIDTH_API_TOKEN!,6 basicAuthPassword: process.env.BANDWIDTH_API_SECRET!,7});89export const messagesApi = new MessagesApi(client);10export const voiceApi = new VoiceApi(client);11export const accountId = process.env.BANDWIDTH_ACCOUNT_ID!;12export const fromNumber = process.env.BANDWIDTH_FROM_NUMBER!;Pro tip: Bandwidth's SDK uses basic auth with your API token as the username and API secret as the password — not a single API key. Both values are required. If the client initializes without error but API calls fail with 401, check that both token AND secret are set correctly.
Expected result: The @bandwidth/sdk package is installed, .env has all four credential placeholders, and lib/bandwidth.ts exports the configured SDK clients ready for use in API routes.
Send SMS Messages via the Messaging V2 API
Send SMS Messages via the Messaging V2 API
Bandwidth's Messaging V2 API supports SMS (text only) and MMS (text plus media attachments). The createMessage method on the MessagesApi client accepts a message request object with from (your Bandwidth number), to (array of recipient numbers in E.164 format), text (message body), applicationId (your Bandwidth messaging application ID), and optionally media (array of media URLs for MMS). The API is asynchronous: you get a response immediately with a message ID, but the actual delivery happens through Bandwidth's network and final delivery status is reported via a webhook callback to your configured status callback URL. For outbound-only notifications, the immediate response with the message ID is sufficient — you know the message was accepted into Bandwidth's delivery queue. For tracking delivery status, you need the status callback webhook configured in your Bandwidth Dashboard under your messaging application. During development in Bolt's WebContainer, the createMessage call goes from your API route to Bandwidth's servers via outbound HTTP — this works in the WebContainer and you will see real messages delivered to real phone numbers. The inbound message webhook and delivery status callbacks require your deployed URL and cannot be tested in the WebContainer preview. Test outbound messaging in the preview, then deploy to test the complete bidirectional flow.
Create an API route at /api/send-sms that accepts POST requests with: to (E.164 phone number string), message (text body), and optional mediaUrls (array of strings). Use the messagesApi from lib/bandwidth.ts to call createMessage with my accountId, BANDWIDTH_MESSAGING_APP_ID, fromNumber, to array, and text. For MMS, include the mediaUrls array. Return the messageId on success. Add proper TypeScript types. Handle errors from the Bandwidth SDK and return a 400 with the error message.
Paste this in Bolt.new chat
1// app/api/send-sms/route.ts2import { NextRequest, NextResponse } from 'next/server';3import { messagesApi, accountId, fromNumber } from '@/lib/bandwidth';4import { MessageRequest } from '@bandwidth/sdk';56export async function POST(request: NextRequest) {7 const { to, message, mediaUrls } = await request.json();89 if (!to || !message) {10 return NextResponse.json({ error: 'Missing required fields: to, message' }, { status: 400 });11 }1213 // Validate E.164 format14 const e164Regex = /^\+1[2-9]\d{9}$/;15 if (!e164Regex.test(to)) {16 return NextResponse.json({ error: 'Phone number must be in E.164 format: +1XXXXXXXXXX' }, { status: 400 });17 }1819 try {20 const body: MessageRequest = {21 from: fromNumber,22 to: [to],23 text: message,24 applicationId: process.env.BANDWIDTH_MESSAGING_APP_ID!,25 ...(mediaUrls && mediaUrls.length > 0 ? { media: mediaUrls } : {}),26 };2728 const response = await messagesApi.createMessage(accountId, body);29 const messageId = response.result.id;3031 return NextResponse.json({ messageId, status: 'queued' });32 } catch (error: unknown) {33 const errorMessage =34 error instanceof Error ? error.message : 'Failed to send message';35 return NextResponse.json({ error: errorMessage }, { status: 400 });36 }37}Pro tip: Bandwidth US phone numbers must be in E.164 format: +1 followed by exactly 10 digits (e.g., +12025551234). Missing the country code +1 causes a 400 error. Validate E.164 format before calling the API.
Expected result: POST to /api/send-sms with a valid E.164 number and message text sends a real SMS to the recipient. The response includes a messageId. You can verify delivery in your Bandwidth Dashboard → Messages.
Handle Inbound Messages with Webhooks
Handle Inbound Messages with Webhooks
When a customer sends an SMS to your Bandwidth number, Bandwidth POSTs an event to the inbound message callback URL configured in your messaging application. The payload includes the from number, to number, message body, message ID, and whether it contains media attachments. Configuring this callback URL requires a publicly accessible endpoint — it cannot be a WebContainer preview URL. The workflow is: deploy your Bolt app first, then go to your Bandwidth Dashboard → Applications → your messaging application → Message Callback, and enter your deployed webhook URL. In Bandwidth's callback format, the payload wraps the message data in a message object with type and message fields. The type field indicates the event type: message-received for inbound messages, message-delivered for delivery confirmations, and message-failed for delivery failures. Parse these in your webhook handler and route them to the appropriate logic. For inbound messages, store them in Supabase and notify your team (via a Slack message, email, or real-time Supabase channel). For a two-way SMS system, you need both this inbound webhook and the outbound /api/send-sms route from the previous step. The WebContainer limitation is relevant here: you cannot receive these inbound message callbacks during development. Build and test the inbound handler code in Bolt, deploy, register the webhook URL, then test by sending a text to your Bandwidth number.
Create a webhook handler at /api/bandwidth/inbound that accepts POST requests from Bandwidth when inbound SMS messages arrive. Parse the Bandwidth message callback payload — the message is inside a message object with type 'message-received'. Extract: from (sender number), to (your number), text (message body), id (message ID). Store each inbound message in a Supabase table sms_messages with columns: id (uuid), direction ('inbound'), phone_number (from), body (text), bandwidth_id (message ID), created_at. Always return HTTP 200 to acknowledge receipt. Also handle message-delivered and message-failed event types by updating the status of the corresponding outbound message in sms_messages.
Paste this in Bolt.new chat
1// app/api/bandwidth/inbound/route.ts2import { NextRequest, NextResponse } from 'next/server';3import { createClient } from '@supabase/supabase-js';45const supabase = createClient(6 process.env.NEXT_PUBLIC_SUPABASE_URL!,7 process.env.SUPABASE_SERVICE_ROLE_KEY!8);910interface BandwidthCallback {11 type: string;12 time: string;13 message: {14 id: string;15 owner: string;16 from: string;17 to: string[];18 text: string;19 direction: string;20 };21}2223export async function POST(request: NextRequest) {24 const payload: BandwidthCallback[] = await request.json();2526 for (const event of payload) {27 if (event.type === 'message-received') {28 const { id, from, to, text } = event.message;29 await supabase.from('sms_messages').insert({30 direction: 'inbound',31 phone_number: from,32 to_number: to[0],33 body: text,34 bandwidth_id: id,35 });36 } else if (event.type === 'message-delivered' || event.type === 'message-failed') {37 const status = event.type === 'message-delivered' ? 'delivered' : 'failed';38 await supabase39 .from('sms_messages')40 .update({ status })41 .eq('bandwidth_id', event.message.id);42 }43 }4445 // Always return 200 to acknowledge receipt46 return NextResponse.json({ received: true });47}Pro tip: Bandwidth sends message callbacks as an array of event objects, not a single object. Always iterate over the payload array, even for single events. Returning non-200 status causes Bandwidth to retry the delivery.
Expected result: After deploying and registering the webhook URL in Bandwidth Dashboard, sending a text to your Bandwidth number triggers the webhook and stores the message in Supabase.
Initiate Voice Calls with BXML
Initiate Voice Calls with BXML
Bandwidth's Voice API lets you initiate outbound calls programmatically and control the call using BXML (Bandwidth Extensible Markup Language). BXML is similar to Twilio's TwiML — XML documents that tell Bandwidth what to do during an active call: speak text, play audio files, gather digits from the keypad, or transfer to another number. The voice call flow works like this: your API route calls Bandwidth's Voice API to create an outbound call, specifying the to number, from number, and an answerUrl — a URL on your server that Bandwidth fetches when the call is answered. Bandwidth fetches the answerUrl via HTTP GET (or POST) and your server returns a BXML document. The BXML content controls the call: a SpeakSentence verb converts text to speech using Bandwidth's TTS engine, a PlayAudio verb streams an audio file, a Gather verb collects DTMF keypad input, and a Hangup verb ends the call. Creating a BXML endpoint in your Bolt app means building a route that returns XML with the correct Content-Type header (application/xml). Like the messaging webhooks, the answerUrl must be publicly accessible — Bandwidth's servers call it when the phone is answered. This means voice call completion requires a deployed URL. You can initiate the call from the WebContainer (the createCall API call is outbound), but the call will fail when Bandwidth tries to fetch BXML from your answerUrl if it is not a public URL.
Create two routes for Bandwidth voice calls. First, create /api/make-call (POST) that accepts 'to' (E.164 number) and 'message' (text to speak). Use the voiceApi from lib/bandwidth.ts to call createCall with: from BANDWIDTH_FROM_NUMBER, to, and answerUrl pointing to my deployed URL /api/bandwidth/bxml with the message as a base64-encoded query parameter. Second, create /api/bandwidth/bxml (GET) that reads the message query param and returns XML with Content-Type application/xml containing a Bandwidth BXML Response with a SpeakSentence verb using the message text and a Hangup verb.
Paste this in Bolt.new chat
1// app/api/bandwidth/bxml/route.ts2import { NextRequest, NextResponse } from 'next/server';34export async function GET(request: NextRequest) {5 const { searchParams } = new URL(request.url);6 const message = searchParams.get('message') || 'Hello, this is an automated message.';7 const decodedMessage = Buffer.from(message, 'base64').toString('utf-8');89 const bxml = `<?xml version="1.0" encoding="UTF-8"?>10<Response>11 <SpeakSentence voice="Susan" locale="en_US" gender="female">${decodedMessage}</SpeakSentence>12 <Pause duration="1" />13 <Hangup />14</Response>`;1516 return new NextResponse(bxml, {17 headers: { 'Content-Type': 'application/xml' },18 });19}Pro tip: Encode the message as base64 in the answerUrl query parameter to avoid URL encoding issues with special characters. Decode it with Buffer.from(message, 'base64').toString() in the BXML route.
Expected result: After deploying, POST to /api/make-call initiates a real phone call to the specified number. When answered, the caller hears the text-to-speech message and the call ends with a hangup.
Common use cases
SMS Notification System
Send automated SMS notifications to users when application events occur — order confirmations, appointment reminders, verification codes, or status updates. The API route receives the event trigger from your application and sends the SMS through Bandwidth's Messaging V2 API with low latency and high deliverability.
Add SMS notifications to my app using Bandwidth. Create an API route at /api/send-sms that accepts POST requests with to (phone number in E.164 format like +12025551234), message (string), and an optional mediaUrl (for MMS). Use the @bandwidth/sdk package with my BANDWIDTH_ACCOUNT_ID, BANDWIDTH_API_TOKEN, and BANDWIDTH_API_SECRET from .env. Send from my BANDWIDTH_FROM_NUMBER. Return the message ID on success. Call this route when a user places an order to send a confirmation SMS.
Copy this prompt to try it in Bolt.new
Two-Way SMS Customer Support
Build a customer support SMS channel that receives inbound messages from customers, stores them in Supabase, and notifies your support team. Agents reply through your Bolt app and the reply is sent via Bandwidth. The inbound webhook receives messages from customers and the API route sends replies.
Build a two-way SMS support system. Create an API route at /api/bandwidth/inbound that accepts POST from Bandwidth when a customer sends a message to my number. Parse the from, to, and text fields from the webhook payload. Store each message in a Supabase table sms_conversations with fields: id, direction (inbound/outbound), phone_number, body, bandwidth_message_id, created_at. Create a /support page that shows all SMS conversations grouped by phone number. Add a reply form that posts to /api/send-sms to send a response via Bandwidth.
Copy this prompt to try it in Bolt.new
Outbound Voice Notification Calls
Initiate automated outbound voice calls using Bandwidth's Voice API to read a text-to-speech message to the recipient. Useful for high-urgency alerts, two-factor authentication via call, or appointment reminders for users who prefer voice over SMS.
Create an API route at /api/make-call that accepts POST requests with to (E.164 phone number) and message (text to speak). Use the Bandwidth Voice API to initiate an outbound call from my BANDWIDTH_FROM_NUMBER. The answerUrl should point to /api/bandwidth/bxml?message={encoded_message} which returns BXML to play the message as text-to-speech. Use @bandwidth/sdk. Store the call SID in my Supabase calls table. This should work for sending automated alerts.
Copy this prompt to try it in Bolt.new
Troubleshooting
API calls return 401 Unauthorized with 'API credentials not valid'
Cause: The BANDWIDTH_API_TOKEN or BANDWIDTH_API_SECRET is incorrect, or the credentials are associated with a different subaccount than the BANDWIDTH_ACCOUNT_ID provided.
Solution: Verify all three credentials in your Bandwidth Dashboard → API Credentials. The Account ID is your main account number (numeric), not a subaccount name. Ensure the API token and secret pair were generated together and have not been regenerated since you copied them — regenerating creates a new secret, invalidating the old one.
Sending a message returns 'The specified application ID does not exist'
Cause: The BANDWIDTH_MESSAGING_APP_ID does not match a messaging application configured in your Bandwidth account, or you are using the wrong account ID with the application ID.
Solution: In Bandwidth Dashboard → Applications, find your messaging application and copy its Application ID. This is a UUID-format string, not the account ID. Add it to .env as BANDWIDTH_MESSAGING_APP_ID and include it in the applicationId field of createMessage requests.
Inbound webhooks and voice call BXML URLs are not being reached even after deployment
Cause: The webhook URLs in Bandwidth Dashboard point to the Bolt WebContainer preview URL instead of the deployed Netlify or Vercel URL, or the deployed app has not started up before Bandwidth makes the request.
Solution: Confirm your webhook URLs in Bandwidth Dashboard → Applications use your deployed domain (e.g., https://your-app.netlify.app/api/bandwidth/inbound), not a *.stackblitz.io or localhost URL. After deploying, wait for the first deployment to complete and verify the URL is accessible in a browser before registering it with Bandwidth.
Voice call is initiated but immediately fails with a call status of 'error' or 'rejected'
Cause: Bandwidth cannot reach the answerUrl to fetch BXML instructions, or the BXML returned is malformed and Bandwidth cannot parse it.
Solution: Test your BXML endpoint directly in a browser — navigate to the answerUrl and verify it returns valid XML with Content-Type application/xml. Ensure the BXML document starts with the XML declaration and uses the correct Bandwidth BXML verb names (SpeakSentence, not Say). Check Bandwidth Dashboard → Voice → Call Logs for error details on the failed call.
Best practices
- Store all Bandwidth credentials server-side only — Account ID, API token, and API secret should never appear in client-side code or VITE_/NEXT_PUBLIC_ variables
- Validate phone numbers are in E.164 format (+1XXXXXXXXXX for US) before calling the API — invalid format causes immediate 400 errors that are confusing to debug
- Always return HTTP 200 from Bandwidth webhook handlers immediately, even before completing database writes — Bandwidth will retry if it receives non-200 responses
- Process Bandwidth webhook callbacks as arrays — the payload is always an array of event objects, even for single events
- Log the Bandwidth message ID returned from createMessage in your database for every outbound message — you need it to correlate delivery status callbacks with the original send
- Test outbound messaging in the Bolt WebContainer to verify SMS delivery works, then deploy before testing inbound message webhooks and voice call BXML
- Use Bandwidth's Messaging V2 API (not the deprecated V1) — V1 uses different endpoints and authentication, and all new integrations should use V2
Alternatives
Twilio has a larger global footprint, more extensive documentation, and a broader developer ecosystem — ideal for international messaging, while Bandwidth excels for high-volume US carrier-grade messaging.
Plivo offers competitive SMS and voice API pricing for high-volume use cases with a simpler API design, though without Bandwidth's Tier 1 carrier ownership advantage.
Vonage provides global messaging and voice with strong European coverage and a comprehensive unified communications API, making it suitable for international multi-channel applications.
Sinch specializes in A2P messaging and verification with competitive global pricing and a unified API that combines SMS, voice, and chat channels.
Frequently asked questions
What makes Bandwidth different from Twilio for SMS in a Bolt app?
Bandwidth owns and operates their own Tier 1 telecom network in the US and Canada, while Twilio resells capacity from other carriers. For developers, this means Bandwidth can offer better deliverability and pricing for high-volume US messaging. Twilio has a larger global footprint, more tutorials, and better documentation. For most Bolt apps starting out, Twilio is easier to get started with; Bandwidth becomes advantageous as volume grows or when carrier-direct routing matters.
Can I send and receive SMS in Bolt's WebContainer during development?
Outbound SMS — sending messages from your API route to Bandwidth's API — works in the WebContainer because it is an outbound HTTP request. You can send real SMS messages during development and see them delivered. Inbound SMS (receiving messages from customers) requires a publicly accessible webhook URL and cannot be tested in the WebContainer. Deploy to Netlify or Vercel first, then register your deployed URL as the inbound message callback in Bandwidth Dashboard.
Do I need separate Bandwidth applications for messaging and voice?
Yes. Bandwidth uses separate application objects for messaging and voice, each with their own application ID and callback URL settings. Create one messaging application in the Bandwidth Dashboard for your SMS/MMS features and a separate voice application for call features. Store both application IDs in your .env file as BANDWIDTH_MESSAGING_APP_ID and BANDWIDTH_VOICE_APP_ID.
How do I test Bandwidth voice calls during development without deploying?
You can initiate a voice call from the WebContainer (the createCall API call is outbound and works fine), but the call will fail because Bandwidth cannot reach your answerUrl to fetch BXML instructions. Use a tool like ngrok locally during development to expose your local server with a public URL, then register that ngrok URL as the answerUrl for testing. For production, use your deployed Netlify or Vercel URL.
Does Bandwidth support toll-free numbers for SMS?
Yes, Bandwidth supports toll-free number provisioning for SMS in the US and Canada. Toll-free numbers work well for business SMS because recipients see a recognizable 800/888/877 number rather than a local number. Provision toll-free numbers through the Bandwidth Dashboard under Numbers, and use the same Messaging V2 API integration — the code is identical regardless of whether the from number is a local long code or a toll-free number.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation