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

Sinch

Send and schedule SMS through the Sinch SMS API with the SMS SDK sinch() adapter, and receive delivery reports and inbound messages with Basic or HMAC callback authentication.

The sinch() adapter sends and schedules SMS through the Sinch SMS REST API and parses Sinch delivery reports and inbound messages. To compare it with other providers, see Choose a provider.

Support status

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

  • No MMS. mediaUrls throws UnsupportedFieldError before any request.
  • Only a few rejections are classified (see Error codes). Everything else is request, which never falls back.
  • Sinch’s SMS docs do not publish the HMAC callback format. Basic callback credentials are the documented option. See Webhooks.

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
SINCH_SERVICE_PLAN_ID Yes Your service plan ID, used in the API path.
SINCH_API_TOKEN Yes API token, sent as a Bearer token.
SINCH_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 { sinch } from "@opencoredev/sms-sdk/sinch";

const sms = createSmsClient({
  adapters: [
    sinch({
      servicePlanId: process.env.SINCH_SERVICE_PLAN_ID ?? "",
      apiToken: process.env.SINCH_API_TOKEN ?? "",
      from: "+15550100001",
    }),
  ],
});

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

The adapter sends POST /xms/v1/{servicePlanId}/batches. providerId is the batch ID, and delivery reports refer to the same ID. sinch() throws ConfigurationError when servicePlanId or apiToken is empty. baseUrl (default https://sms.api.sinch.com) and fetch are optional.

Senders

Sinch sends from a long code, a toll-free number, a short code, or an alphanumeric sender ID ({ senderId: "Acme" }). { messagingService } is not supported. Which senders your account may use depends on the destination country; see Registration and consent.

Features

Feature Support
MMS (mediaUrls) No
Scheduling (sendAt) Yes, sent as send_at
Validity period (validityPeriodSec) 1 to 2,592,000 seconds, sent as expire_at counted from the moment of the request
Per-message webhookUrl Yes, sent as callback_url
Empty body Not allowed
Native idempotency No. Use an idempotency store.

Provider options

Pass Sinch-only fields under providerOptions.sinch:

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

await sms.send({
  to: "+14155550123",
  body: "Your order has shipped.",
  providerOptions: { sinch: { deliveryReport: "per_recipient", clientReference: "order-123" } },
});
Option Wire field Type
deliveryReport delivery_report "none" | "per_recipient" | "per_recipient_final" | "summary" | "full"
clientReference client_reference string, up to 128 characters
maxNumberOfMessageParts max_number_of_message_parts integer, 1 to 255

Set deliveryReport: "per_recipient" to receive a status webhook for each message.

Webhooks

Sinch authenticates callbacks in one of two ways, configured on your callback URL or service plan. Point Sinch at your handler with a per-message webhookUrl or the callback URL on the service plan, then pass the matching credentials:

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: "sinch",
      request,
      credentials: {
        kind: "basic",
        username: process.env.SINCH_CALLBACK_USERNAME ?? "",
        password: process.env.SINCH_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;
  }
}
  • Basic ({ kind: "basic", username, password }): Sinch documents this for SMS callbacks. Use it unless you have a reason not to. Basic requests carry no timestamp, so there is no replay window.
  • HMAC ({ kind: "hmac", webhookSecret }): Sinch sets this up through your account manager and does not publish the SMS format. The parser uses the scheme Sinch documents for its Conversation API: a base64 HMAC-SHA256 of body.nonce.timestamp from the x-sinch-webhook-signature, -nonce, and -timestamp headers, with a 300-second timestamp window (toleranceSec). Confirm the format with Sinch before relying on it.

OAuth 2.0 callbacks are not supported. Sinch can send a report more than once and out of order, so store event.dedupeKey and skip repeats. The key is sinch:{batchId}:{recipient}:{status} for delivery reports and sinch:{messageId}:received for inbound messages.

Sinch status SMS SDK event
inbound mo_text, mo_binary, inbound_sms message.received, or recipient.opted_out / recipient.help for STOP and HELP keywords
Queued, Pending message.queued
Sent, Accepted message.sent
Delivered message.delivered
Failed, Undelivered, Expired message.undelivered
Rejected or Filtered with code 40003 message.filtered
Rejected or Filtered with any other code message.undelivered
anything else unrecognized

Limitations

  • No MMS. Use Twilio, Telnyx, or Plivo.
  • OAuth 2.0 callback authentication is not supported.
  • The HMAC callback format is unconfirmed for SMS. Prefer Basic credentials.

Error codes

Sinch response Category Falls back
401 or 403 auth Yes
429 with code 429 or TOO_MANY_REQUESTS rate_limited Yes, after retries
Code 40004 or 40005 sender Yes
Code 40301 or 40302 account Yes
Code 40002 or 40003 recipient No
Any other 4xx request No
429 without the rate-limit code unknown Never
5xx, network error, timeout, or 2xx without a batch ID unknown Never

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

Official documentation

Next steps