Skip to content

JavaScript SDK

Official JavaScript/TypeScript package for integrating with the CubeConnect WhatsApp Business API.

Requirements

  • Node.js 18+ or modern browser
  • ES modules support

Installation

bash
npm install @cubesoftware/cube-connect-sdk-js

Configuration

javascript
import { CubeConnect } from '@cubesoftware/cube-connect-sdk-js'

const cube = new CubeConnect({
  apiKey: process.env.CUBECONNECT_API_KEY,                        // Settings → API
  whatsappAccountId: process.env.CUBECONNECT_WHATSAPP_ACCOUNT_ID, // Dashboard → WhatsApp Numbers → API ID:
})

Optional settings:

javascript
const cube = new CubeConnect({
  apiKey: process.env.CUBECONNECT_API_KEY,
  whatsappAccountId: process.env.CUBECONNECT_WHATSAPP_ACCOUNT_ID,
  baseUrl: 'https://cubeconnect.io', // default
  timeout: 30000,                    // 30 seconds (default)
})

Multiple WhatsApp Numbers

If your account has more than one connected number, set the default in the constructor and override it per call using options.whatsappAccountId:

javascript
const cube = new CubeConnect({
  apiKey: process.env.CUBECONNECT_API_KEY,
  whatsappAccountId: '01JX_DEFAULT',  // used when no override is passed
})

// Send from a different number
await cube.sendTemplate('+966501234567', 'offer_reminder', 'ar', ['50%'], {
  whatsappAccountId: '01JX_MARKETING',
})

// List templates for a specific number
const templates = await cube.getTemplates({ whatsappAccountId: '01JX_MARKETING' })

// Create a campaign from a specific number
await cube.createCampaign({
  messageType: 'template',
  templateName: 'offer_reminder',
  templateLanguage: 'ar',
  recipients: [...],
  whatsappAccountId: '01JX_MARKETING',
})

Find each number's ID in Dashboard → WhatsApp Numbers → API ID:.

Sending Messages

sendTemplate()

Send a pre-approved template message to any number at any time.

ParameterTypeRequiredDescription
phonestringYesRecipient phone number with country code (e.g., +966501234567)
namestringYesTemplate name (e.g., order_confirmation)
languageCodestringYesLanguage code matching the approved template (e.g., ar, en_US)
paramsstring[]NoParameters mapping to 1, 2, etc. in the template body
options.scheduledAtstringNoISO 8601 datetime for scheduled delivery (e.g., 2026-05-01T10:00:00)
options.timezonestringNoIANA timezone (e.g., Asia/Riyadh). Required when scheduledAt is set
options.whatsappAccountIdstringNoOverride the default WhatsApp account. Useful when managing multiple numbers
javascript
const response = await cube.sendTemplate(
  '+966501234567',       // phone
  'order_confirmation',  // name
  'ar',                  // languageCode
  ['ORD-1234', '500 SAR'], // params → {{1}}, {{2}}
)

response.status               // "queued"
response.messageLogId         // 4521
response.conversationCategory // "UTILITY"

Without parameters:

javascript
const response = await cube.sendTemplate('+966501234567', 'welcome_message', 'ar')

Scheduled delivery:

javascript
const response = await cube.sendTemplate(
  '+966501234567',
  'appointment_reminder',
  'ar',                       // languageCode
  ['Dr. Ahmed', '10:00 AM'],  // params
  {
    scheduledAt: '2026-05-01T09:00:00', // ISO 8601
    timezone: 'Asia/Riyadh',            // IANA timezone
  },
)

response.status      // "scheduled"
response.scheduledAt // "2026-05-01T06:00:00Z" (UTC)

Override the sending number (when you manage multiple WhatsApp numbers):

javascript
const response = await cube.sendTemplate(
  '+966501234567',
  'order_confirmation',
  'ar',
  ['ORD-1234'],
  { whatsappAccountId: 'account_id_2' }, // overrides the default
)

Get Message Status

getMessageStatus()

ParameterTypeRequiredDescription
messageLogIdnumberYesThe messageLogId returned when the message was sent
javascript
const sent = await cube.sendTemplate('+966501234567', 'order_confirmation', 'ar', ['ORD-1234'])

const status = await cube.getMessageStatus(sent.messageLogId)

status.status        // "delivered"
status.toPhone       // "966501234567"
status.metaMessageId // "wamid.HBgN..."
status.sentAt        // "2026-05-01T07:05:00Z"
status.isDelivered() // true
status.isRead()      // false

TIP

For real-time updates, configure a webhook to receive message.status_updated events instead of polling.

List Templates

getTemplates()

ParameterTypeRequiredDescription
options.statusstringNoFilter by approval status. Use APPROVED to fetch only sendable templates
options.whatsappAccountIdstringNoOverride the default WhatsApp account
javascript
const templates = await cube.getTemplates({ status: 'APPROVED' })

templates.forEach(t => {
  t.name        // "order_confirmation"
  t.language    // "ar"
  t.category    // "UTILITY"
  t.status      // "APPROVED"
  t.paramsCount // 3
  t.body        // "Hello {{1}}, your order {{2}} has been shipped."
  t.header      // null
})

All templates (no filter):

javascript
const templates = await cube.getTemplates()

Templates for a specific number:

javascript
const templates = await cube.getTemplates({ status: 'APPROVED', whatsappAccountId: 'account_id_2' })

Bulk Campaigns

createCampaign()

Send a pre-approved template to a large list in a single API call. The whatsappAccountId set in the constructor is used automatically. Pass whatsappAccountId in the payload to override it.

ParameterTypeRequiredDescription
messageTypestringYesMust be template
templateNamestringYesTemplate name (e.g., order_confirmation)
templateLanguagestringYesLanguage code matching the approved template (e.g., ar, en_US)
recipientsarrayYesList of recipients. Max 50,000 per call
recipients[].phonestringYesRecipient phone number with country code
recipients[].namestringNoRecipient display name
recipients[].variablesobjectNoPer-recipient template variables (e.g., { '1': 'Ahmed', '2': 'ORD-1234' })
campaignNamestringNoHuman-readable campaign name
scheduledAtstringNoISO 8601 datetime for scheduled delivery
timezonestringNoIANA timezone. Required when scheduledAt is set
whatsappAccountIdstringNoOverride the default WhatsApp account
javascript
const campaign = await cube.createCampaign({
  messageType: 'template',
  templateName: 'order_confirmation',  // same name used in sendTemplate()
  templateLanguage: 'ar',              // same languageCode used in sendTemplate()
  recipients: [
    { phone: '+966501234567', name: 'Ahmed', variables: { '1': 'Ahmed', '2': 'ORD-1234', '3': 'CUBE20' } },
    { phone: '+966509876543', name: 'Sara',  variables: { '1': 'Sara',  '2': 'ORD-5678', '3': 'CUBE15' } },
  ],
  campaignName: 'Offer Reminder',
  scheduledAt: '2026-05-01T09:00:00', // optional
  timezone: 'Asia/Riyadh',            // required when scheduledAt is set
})

campaign.campaignId    // "01JX..."
campaign.status        // "pending"
campaign.totalCount    // 2
campaign.isScheduled() // true

getCampaign()

ParameterTypeRequiredDescription
campaignIdstringYesThe campaignId returned from createCampaign()
javascript
const campaign = await cube.getCampaign(campaignId)

campaign.status        // "processing", "completed", "cancelled"
campaign.totalCount    // 500
campaign.sentCount     // 320
campaign.failedCount   // 12
campaign.isCompleted() // true

cancelCampaign()

ParameterTypeRequiredDescription
campaignIdstringYesThe campaignId of a pending campaign to cancel
javascript
const result = await cube.cancelCampaign(campaignId)
result.success // true

Health Check

javascript
const health = await cube.health()

if (health.status === 'healthy') {
  // Platform is operational
}

Response Objects

MessageResponse

Returned by sendTemplate():

PropertyTypeDescription
statusstringqueued for immediate, scheduled for deferred delivery
messageLogIdnumberUnique tracking ID
conversationCategorystringMARKETING, UTILITY, or AUTHENTICATION
costnumberMessage cost
scheduledAtstring|nullUTC datetime if scheduled, otherwise null
javascript
response.queued()    // true if status is "queued"
response.scheduled() // true if status is "scheduled"
response.toObject()  // Plain object representation

MessageStatusResponse

Returned by getMessageStatus():

PropertyTypeDescription
messageLogIdnumberUnique message ID
statusstringCurrent delivery status
toPhonestringRecipient phone number
messageTypestringtemplate
metaMessageIdstring|nullWhatsApp message ID (after sending)
sentAtstring|nullUTC datetime when sent
scheduledAtstring|nullScheduled UTC datetime
costAmountnumberMessage cost
costCurrencystringCurrency code
errorMessagestring|nullError details if failed
createdAtstringLog creation timestamp
javascript
status.isSent()      // true if status is "sent"
status.isDelivered() // true if status is "delivered"
status.isRead()      // true if status is "read"
status.isFailed()    // true if status is "failed"
status.isScheduled() // true if status is "scheduled"
status.toObject()    // Plain object representation

CampaignResponse

Returned by createCampaign() and getCampaign():

PropertyTypeDescription
campaignIdstringUnique campaign ULID
namestring|nullCampaign name
statusstringpending, processing, completed, cancelled, failed
totalCountnumberTotal recipients
sentCountnumberSuccessfully sent
failedCountnumberFailed deliveries
scheduledAtstring|nullScheduled UTC datetime
createdAtstringCreation timestamp
javascript
campaign.isScheduled()  // true if pending with a scheduledAt
campaign.isCompleted()  // true if status is "completed"
campaign.isCancelled()  // true if status is "cancelled"
campaign.toObject()     // Plain object representation

Error Reference

HTTPError CodeCause
401AUTHENTICATION_REQUIREDNo API key provided in the request
401INVALID_API_KEYAPI key is invalid or has been revoked
403FORBIDDENAPI key does not have permission for this action
403API_KEY_NO_TENANTAPI key is not linked to any account
404NOT_FOUNDThe requested resource does not exist
404TEMPLATE_NOT_FOUNDTemplate name not found in your account
422VALIDATION_ERRORRequest failed input validation — check error.details for field-level errors
422INVALID_PHONE_NUMBERPhone number is not in a valid international format
422NO_ACTIVE_ACCOUNTNo connected WhatsApp number found for the given whatsapp_account_id
422MISSING_ACCESS_TOKENThe selected WhatsApp number has no Meta access token configured
422TEMPLATE_LANGUAGE_MISMATCHLanguage code does not match any approved version of this template
422TEMPLATE_PARAMS_MISMATCHFewer parameters provided than the template requires
429RATE_LIMIT_EXCEEDEDToo many API requests — apply exponential backoff and retry
429PLAN_LIMIT_REACHEDMonthly message quota reached — upgrade your plan
429SUBSCRIPTION_EXPIREDSubscription has expired
500MESSAGE_SEND_FAILEDWhatsApp API rejected or failed to deliver the message
500INTERNAL_ERRORUnexpected server error — contact support if this persists
503SERVICE_DEGRADEDOne or more platform services are temporarily unavailable

Error Handling

All errors include an errorCode property matching the API error codes:

javascript
import {
  CubeConnect,
  AuthenticationError,
  ValidationError,
  RateLimitError,
  NotFoundError,
  CubeConnectError,
} from '@cubesoftware/cube-connect-sdk-js'

try {
  await cube.sendTemplate('+966501234567', 'order_confirmation', 'ar', ['ORD-1234'])
} catch (e) {
  if (e instanceof AuthenticationError) {
    // 401/403 — Invalid API key or permissions
    e.errorCode  // "INVALID_API_KEY", "FORBIDDEN", ...
    e.statusCode // 401 or 403
  } else if (e instanceof ValidationError) {
    // 422 — Invalid request data
    e.errorCode // "VALIDATION_ERROR", "TEMPLATE_LANGUAGE_MISMATCH", ...
    e.errors    // { available_languages: ['ar'] }
  } else if (e instanceof NotFoundError) {
    // 404 — Resource not found
    e.errorCode // "NOT_FOUND", "TEMPLATE_NOT_FOUND"
  } else if (e instanceof RateLimitError) {
    // 429 — Rate or plan limit exceeded
    e.errorCode // "RATE_LIMIT_EXCEEDED", "PLAN_LIMIT_REACHED", ...
  } else if (e instanceof CubeConnectError) {
    // 5xx or network errors
    e.errorCode  // "INTERNAL_ERROR", "MESSAGE_SEND_FAILED", ...
    e.statusCode
  }
}

TypeScript

Full type definitions are included:

typescript
import type {
  CubeConnectOptions,
  HealthResponse,
  MessageResponseData,
  MessageStatusResponseData,
  SendOptions,
  CreateCampaignPayload,
  CampaignRecipient,
  CampaignResponseData,
  TemplateData,
} from '@cubesoftware/cube-connect-sdk-js'

import { CampaignResponse, MessageStatusResponse } from '@cubesoftware/cube-connect-sdk-js'

Webhooks

Receive real-time notifications from CubeConnect for messages, campaigns, templates, chatbot flows, and quality events.

Signature Verification

javascript
import { verifyWebhookSignature } from '@cubesoftware/cube-connect-sdk-js'

app.post('/cubeconnect/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const isValid = verifyWebhookSignature({
    payload:   req.body.toString(),
    signature: req.headers['x-webhook-signature'],
    timestamp: req.headers['x-webhook-timestamp'],
    secret:    process.env.CUBECONNECT_WEBHOOK_SECRET,
  })

  if (!isValid) return res.status(401).send('Invalid signature')

  // Process webhook...
  res.sendStatus(200)
})

Handling Events

javascript
import { WebhookEvent } from '@cubesoftware/cube-connect-sdk-js'

const event = new WebhookEvent(JSON.parse(body))

if (event.isMessageReceived()) {
  console.log(`New message from ${event.get('from')}: ${event.get('content')}`)
}

if (event.isCampaignCompleted()) {
  console.log(`Campaign "${event.get('name')}": ${event.get('sent_count')} sent`)
}

if (event.isTemplateStatusChanged()) {
  console.log(`Template "${event.get('template_name')}" is now ${event.get('status')}`)
}

See Webhook Events for full payload examples and Signature Verification for security details.

Real-World Examples

E-Commerce Order Confirmation

javascript
import { CubeConnect } from '@cubesoftware/cube-connect-sdk-js'

const cube = new CubeConnect({ apiKey: process.env.CUBECONNECT_API_KEY })

async function confirmOrder(order) {
  await cube.sendTemplate(
    order.customerPhone,                       // phone
    'order_confirmation',                      // name
    'ar',                                      // languageCode
    [order.reference, `${order.total} SAR`],   // params
  )
}

OTP Verification

javascript
const code = String(Math.floor(Math.random() * 1000000)).padStart(6, '0')

await cube.sendTemplate(phone, 'verification_code', 'ar', [code])

With Retry on Rate Limit

javascript
import { CubeConnect, RateLimitError } from '@cubesoftware/cube-connect-sdk-js'

const cube = new CubeConnect({ apiKey: process.env.CUBECONNECT_API_KEY })

async function sendWithRetry(phone, templateName, languageCode, params, retries = 3) {
  for (let i = 0; i < retries; i++) {
    try {
      return await cube.sendTemplate(phone, templateName, languageCode, params)
    } catch (e) {
      if (e instanceof RateLimitError && i < retries - 1) {
        await new Promise(r => setTimeout(r, 2000 * (i + 1)))
        continue
      }
      throw e
    }
  }
}

Source Code

GitHub: github.com/CubeSoftLabs/cube-connect-sdk-js

CubeConnect WhatsApp Business Platform