Appearance
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-jsConfiguration
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.
| Parameter | Type | Required | Description |
|---|---|---|---|
phone | string | Yes | Recipient phone number with country code (e.g., +966501234567) |
name | string | Yes | Template name (e.g., order_confirmation) |
languageCode | string | Yes | Language code matching the approved template (e.g., ar, en_US) |
params | string[] | No | Parameters mapping to 1, 2, etc. in the template body |
options.scheduledAt | string | No | ISO 8601 datetime for scheduled delivery (e.g., 2026-05-01T10:00:00) |
options.timezone | string | No | IANA timezone (e.g., Asia/Riyadh). Required when scheduledAt is set |
options.whatsappAccountId | string | No | Override 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()
| Parameter | Type | Required | Description |
|---|---|---|---|
messageLogId | number | Yes | The 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() // falseTIP
For real-time updates, configure a webhook to receive message.status_updated events instead of polling.
List Templates
getTemplates()
| Parameter | Type | Required | Description |
|---|---|---|---|
options.status | string | No | Filter by approval status. Use APPROVED to fetch only sendable templates |
options.whatsappAccountId | string | No | Override 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
messageType | string | Yes | Must be template |
templateName | string | Yes | Template name (e.g., order_confirmation) |
templateLanguage | string | Yes | Language code matching the approved template (e.g., ar, en_US) |
recipients | array | Yes | List of recipients. Max 50,000 per call |
recipients[].phone | string | Yes | Recipient phone number with country code |
recipients[].name | string | No | Recipient display name |
recipients[].variables | object | No | Per-recipient template variables (e.g., { '1': 'Ahmed', '2': 'ORD-1234' }) |
campaignName | string | No | Human-readable campaign name |
scheduledAt | string | No | ISO 8601 datetime for scheduled delivery |
timezone | string | No | IANA timezone. Required when scheduledAt is set |
whatsappAccountId | string | No | Override 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() // truegetCampaign()
| Parameter | Type | Required | Description |
|---|---|---|---|
campaignId | string | Yes | The 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() // truecancelCampaign()
| Parameter | Type | Required | Description |
|---|---|---|---|
campaignId | string | Yes | The campaignId of a pending campaign to cancel |
javascript
const result = await cube.cancelCampaign(campaignId)
result.success // trueHealth Check
javascript
const health = await cube.health()
if (health.status === 'healthy') {
// Platform is operational
}Response Objects
MessageResponse
Returned by sendTemplate():
| Property | Type | Description |
|---|---|---|
status | string | queued for immediate, scheduled for deferred delivery |
messageLogId | number | Unique tracking ID |
conversationCategory | string | MARKETING, UTILITY, or AUTHENTICATION |
cost | number | Message cost |
scheduledAt | string|null | UTC datetime if scheduled, otherwise null |
javascript
response.queued() // true if status is "queued"
response.scheduled() // true if status is "scheduled"
response.toObject() // Plain object representationMessageStatusResponse
Returned by getMessageStatus():
| Property | Type | Description |
|---|---|---|
messageLogId | number | Unique message ID |
status | string | Current delivery status |
toPhone | string | Recipient phone number |
messageType | string | template |
metaMessageId | string|null | WhatsApp message ID (after sending) |
sentAt | string|null | UTC datetime when sent |
scheduledAt | string|null | Scheduled UTC datetime |
costAmount | number | Message cost |
costCurrency | string | Currency code |
errorMessage | string|null | Error details if failed |
createdAt | string | Log 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 representationCampaignResponse
Returned by createCampaign() and getCampaign():
| Property | Type | Description |
|---|---|---|
campaignId | string | Unique campaign ULID |
name | string|null | Campaign name |
status | string | pending, processing, completed, cancelled, failed |
totalCount | number | Total recipients |
sentCount | number | Successfully sent |
failedCount | number | Failed deliveries |
scheduledAt | string|null | Scheduled UTC datetime |
createdAt | string | Creation 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 representationError Reference
| HTTP | Error Code | Cause |
|---|---|---|
| 401 | AUTHENTICATION_REQUIRED | No API key provided in the request |
| 401 | INVALID_API_KEY | API key is invalid or has been revoked |
| 403 | FORBIDDEN | API key does not have permission for this action |
| 403 | API_KEY_NO_TENANT | API key is not linked to any account |
| 404 | NOT_FOUND | The requested resource does not exist |
| 404 | TEMPLATE_NOT_FOUND | Template name not found in your account |
| 422 | VALIDATION_ERROR | Request failed input validation — check error.details for field-level errors |
| 422 | INVALID_PHONE_NUMBER | Phone number is not in a valid international format |
| 422 | NO_ACTIVE_ACCOUNT | No connected WhatsApp number found for the given whatsapp_account_id |
| 422 | MISSING_ACCESS_TOKEN | The selected WhatsApp number has no Meta access token configured |
| 422 | TEMPLATE_LANGUAGE_MISMATCH | Language code does not match any approved version of this template |
| 422 | TEMPLATE_PARAMS_MISMATCH | Fewer parameters provided than the template requires |
| 429 | RATE_LIMIT_EXCEEDED | Too many API requests — apply exponential backoff and retry |
| 429 | PLAN_LIMIT_REACHED | Monthly message quota reached — upgrade your plan |
| 429 | SUBSCRIPTION_EXPIRED | Subscription has expired |
| 500 | MESSAGE_SEND_FAILED | WhatsApp API rejected or failed to deliver the message |
| 500 | INTERNAL_ERROR | Unexpected server error — contact support if this persists |
| 503 | SERVICE_DEGRADED | One 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
}
}
}