Skip to content
SMS SDK
Esc
↑↓navigate↵open⌘Jpreview
On this page

Bandwidth

Send SMS through the Bandwidth Messaging API v2 with the SMS SDK bandwidth() adapter, and receive delivery and inbound callbacks behind Basic authentication.

The bandwidth() adapter sends SMS through the Bandwidth Messaging API v2 and parses Bandwidth’s delivery and inbound callbacks. To compare it with other providers, see Choose a provider.

Support status

Partial. The adapter passes the shared contract tests, with these limits:

  • SMS only. mediaUrls, sendAt, validityPeriodSec, and a per-message webhookUrl throw UnsupportedFieldError before any request.
  • Bandwidth does not sign callbacks. The webhook parser requires the Basic credentials you set on your Bandwidth application instead.
  • Rejections are classified by HTTP status, and a 429 counts as a rate limit only when Bandwidth wrote the body; any other 429 is an unknown outcome. Bandwidth’s full error code list is not mapped, so most rejections are request and do not fall back. See Error codes.

The same notes are in adapter.support.notes at runtime.

Setup

npm install @opencoredev/sms-sdk
pnpm add @opencoredev/sms-sdk
yarn add @opencoredev/sms-sdk
bun add @opencoredev/sms-sdk
nub add @opencoredev/sms-sdk
aube add @opencoredev/sms-sdk
Variable Required Used for
BANDWIDTH_USER_ID Yes Your Bandwidth account ID, used in the API path.
BANDWIDTH_API_TOKEN, BANDWIDTH_API_SECRET Yes API credentials, sent as HTTP Basic auth.
BANDWIDTH_APPLICATION_ID Yes The messaging application linked to your sending number.
BANDWIDTH_FROM No Default sender.

The adapter reads no environment variables itself. The examples and the doctor CLI use these names.

import { createSmsClient } from "@opencoredev/sms-sdk";
import { bandwidth } from "@opencoredev/sms-sdk/bandwidth";

const sms = createSmsClient({
  adapters: [
    bandwidth({
      userId: process.env.BANDWIDTH_USER_ID ?? "",
      apiToken: process.env.BANDWIDTH_API_TOKEN ?? "",
      apiSecret: process.env.BANDWIDTH_API_SECRET ?? "",
      applicationId: process.env.BANDWIDTH_APPLICATION_ID ?? "",
      from: "+15550100001",
    }),
  ],
});

const result = await sms.send({ to: "+14155550123", body: "Your order has shipped." });
console.log(result.providerId); // the Bandwidth message ID

bandwidth() throws ConfigurationError when any of the four credentials is empty. baseUrl (default https://messaging.bandwidth.com) and fetch are optional.

Senders

Bandwidth sends from a long code, a toll-free number, or a short code. Alphanumeric sender IDs and { messagingService } fail validation before any request. US long codes, toll-free numbers, and short codes need registration with carriers; see Registration and consent.

Features

Feature Support
MMS (mediaUrls) No
Scheduling (sendAt) No
Validity period (validityPeriodSec) No
Per-message webhookUrl No. Callbacks go to the URL on your Bandwidth application.
Text length Up to 2,048 characters, checked before sending
Native idempotency No. Use an idempotency store.

Provider options

Pass Bandwidth-only fields under providerOptions.bandwidth:

// Loading the adapter module adds the typed providerOptions.bandwidth entry.
import "@opencoredev/sms-sdk/bandwidth";
import { sms } from "./sms";

await sms.send({
  to: "+14155550123",
  body: "Your code is 123456",
  providerOptions: { bandwidth: { tag: "otp", priority: "high" } },
});
Option Wire field Type
tag tag string, up to 1,024 characters, returned in callbacks
priority priority "default" | "high"

Webhooks

Bandwidth posts delivery and inbound events to the callback URL on your messaging application. Set callback credentials on that application, then pass the same values to the parser. The credentials are required.

import { parseSmsWebhook, WebhookSignatureError } from "@opencoredev/sms-sdk/webhooks";
import { db } from "./db";

export async function POST(request: Request): Promise<Response> {
  try {
    const event = await parseSmsWebhook({
      provider: "bandwidth",
      request,
      credentials: {
        username: process.env.BANDWIDTH_CALLBACK_USERNAME ?? "",
        password: process.env.BANDWIDTH_CALLBACK_PASSWORD ?? "",
      },
    });
    if (await db.webhookEvents.insertIfNew(event.dedupeKey)) {
      console.log(event.type);
    }
    return new Response(null, { status: 204 });
  } catch (error) {
    if (error instanceof WebhookSignatureError) return new Response(null, { status: 401 });
    throw error;
  }
}

A request without a matching Authorization header throws WebhookSignatureError. The comparison runs in constant time. Bandwidth sends no signature and no timestamp, so there is no replay window: store event.dedupeKey and skip repeats.

Bandwidth event SMS SDK event
message-received message.received, or recipient.opted_out / recipient.help for STOP and HELP keywords
message-sending message.queued
message-delivered message.delivered
message-failed message.undelivered
anything else unrecognized

dedupeKey is bandwidth:{messageId}:{type}.

Limitations

  • No MMS. Use Twilio, Telnyx, or Plivo.
  • No scheduling or validity period. Use Telnyx or Sinch for scheduling, and Twilio, Plivo, Vonage, or Sinch for a validity period.
  • No alphanumeric sender IDs.
  • Callbacks carry no signature, so the Basic credentials and HTTPS are the only proof a request came from Bandwidth.

Error codes

Bandwidth response Category Falls back
401 or 403 auth Yes
404 account Yes
429 with a Bandwidth error body (JSON with type or description) rate_limited Yes, after retries
Any other 4xx request No
429 without a Bandwidth error body, such as a proxy page unknown Never
5xx, network error, timeout, or 2xx without a message ID unknown Never

error.provider.requestId holds the X-Request-Id or X-Correlation-Id header. See Fallback and retries for what each category does.

Official documentation

Next steps