diff --git a/.env.example b/.env.example
index e7f78e2..0694861 100644
--- a/.env.example
+++ b/.env.example
@@ -1,3 +1,7 @@
# CCAI API Configuration
CCAI_CLIENT_ID=your_client_id_here
CCAI_API_KEY=your_api_key_here
+
+# Optional: used by the webhook example server (webhook-server.js / express-webhook.ts)
+# CCAI_WEBHOOK_SECRET=
+# PORT=3000
diff --git a/README.md b/README.md
index 210ab02..738f94f 100644
--- a/README.md
+++ b/README.md
@@ -134,7 +134,7 @@ const uploaded = await ccai.mms.uploadImageToSignedUrl(
// Step 3 — (Optional) Confirm file is available
const stored = await ccai.mms.checkFileUploaded(fileKey);
-console.log('File URL:', stored?.url);
+console.log('File URL:', stored?.storedUrl);
// Step 4a — Send to multiple recipients using the uploaded fileKey
const bulkResponse = await ccai.mms.send(
@@ -164,11 +164,12 @@ const singleResponse = await ccai.mms.sendSingle(
// ── Progress tracking ────────────────────────────────────────────────────────
const options: SMSOptions = {
timeout: 60000,
- retries: 3,
onProgress: (status: string) => console.log(`Progress: ${status}`)
};
```
+`onProgress` is supported by SMS, MMS, and Email sends. `timeout` applies to MMS sends only.
+
### Brands
Register and manage brands for TCR (The Campaign Registry) business verification.
@@ -249,6 +250,8 @@ const campaign = await ccai.campaigns.create({
subUseCases: ['CUSTOMER_CARE', 'TWO_FACTOR_AUTHENTICATION', 'ACCOUNT_NOTIFICATION'],
description: 'Security codes and support messaging.',
messageFlow: 'Users opt-in via signup form at https://example.com/signup',
+ termsLink: 'https://example.com/terms',
+ privacyLink: 'https://example.com/privacy',
hasEmbeddedLinks: true,
hasEmbeddedPhone: false,
isAgeGated: false,
@@ -289,6 +292,8 @@ await ccai.campaigns.delete(campaign.id);
> Note: `MIXED` and `LOW_VOLUME_MIXED` campaigns require 2–3 `subUseCases`.
+> `termsLink` and `privacyLink` are optional fields on `CampaignData`/`CampaignResponse`.
+
#### Sub-Use Cases
`TWO_FACTOR_AUTHENTICATION`, `ACCOUNT_NOTIFICATION`, `CUSTOMER_CARE`, `DELIVERY_NOTIFICATION`, `FRAUD_ALERT`, `MARKETING`, `POLLING_VOTING`
@@ -418,20 +423,15 @@ const webhookConfig: WebhookConfig = {
// secret is optional - if not provided, server generates one automatically
// method?: string (default 'POST')
// integrationType?: string (e.g. 'REST')
- // events?: WebhookEventType[]
};
const webhook = await ccai.webhook.register(webhookConfig);
console.log('Webhook registered with ID:', webhook.id);
console.log('Secret Key:', webhook.secretKey); // Save this securely!
-// Example 2: Register with custom secret and event types
+// Example 2: Register with a custom secret
const webhookCustomConfig: WebhookConfig = {
url: 'https://your-app.com/api/custom-webhook',
- secret: 'your-custom-secret', // optional - user-provided secret
- events: [
- WebhookEventType.MESSAGE_SENT,
- WebhookEventType.MESSAGE_RECEIVED
- ]
+ secret: 'your-custom-secret' // optional - user-provided secret
};
const webhookCustom = await ccai.webhook.register(webhookCustomConfig);
console.log('Custom secret webhook registered:', webhookCustom.id);
@@ -445,8 +445,7 @@ webhooks.forEach(wh => {
// Update a webhook
const updateConfig: WebhookConfig = {
- url: 'https://your-app.com/api/new-webhook',
- events: [WebhookEventType.MESSAGE_SENT]
+ url: 'https://your-app.com/api/new-webhook'
};
const updated = await ccai.webhook.update(webhook.id, updateConfig);
console.log('Updated webhook URL:', updated.url);
@@ -456,7 +455,7 @@ await ccai.webhook.delete(webhook.id);
// Verify webhook signature (in your incoming request handler)
const signature = req.headers['x-ccai-signature'] as string;
-const clientId = ccai.clientId;
+const clientId = ccai.getClientId();
const eventHash = req.body.eventHash as string; // From the webhook payload
const secret = 'your-webhook-secret';
const isValid = ccai.webhook.verifySignature(signature, clientId, eventHash, secret);
@@ -483,6 +482,8 @@ CloudContactAI supports the following webhook event types (available via `Webhoo
| `MESSAGE_ERROR_CARRIER` | `message.error.carrier` | Carrier-side delivery error |
| `MESSAGE_ERROR_CLOUDCONTACT` | `message.error.cloudcontact` | Platform-side delivery error |
+`createWebhookHandler` (below) provides typed callbacks for `MESSAGE_SENT` and `MESSAGE_RECEIVED`. For the other four event types, read `eventType` from the parsed payload — either in the "Simple Webhook Handler" pattern below, or by calling `ccai.webhook.parseEvent()` on the raw request body in your own route handler.
+
#### Event Payload Schema
**Message Sent Event:**
@@ -521,15 +522,14 @@ CloudContactAI supports the following webhook event types (available via `Webhoo
#### Using Webhooks with Next.js
+`createWebhookHandler` routes `message.sent` and `message.received` events to `onMessageSent`/`onMessageReceived`. It does not verify the request signature — for signature verification, use the manual pattern in [Simple Webhook Handler](#simple-webhook-handler) below, or call `ccai.webhook.verifySignature(...)` yourself before your own routing logic.
+
```typescript
// pages/api/ccai-webhook.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { createWebhookHandler, WebhookEventType, type WebhookEvent } from 'ccai-node';
export default createWebhookHandler({
- // Optional: Secret for verifying webhook signatures
- secret: process.env.CCAI_WEBHOOK_SECRET,
-
// Handler for outbound messages (type-safe)
onMessageSent: async (event: WebhookEvent) => {
console.log('Message sent event received:');
@@ -646,6 +646,8 @@ async function sendMessages() {
"+15559876543",
"Hi ${firstName}!",
"Test Campaign",
+ undefined, // customData (optional)
+ undefined, // senderPhone (optional)
options
);
} catch (error) {
diff --git a/src/email/README.md b/src/email/README.md
index cb6f33a..8bca1c1 100644
--- a/src/email/README.md
+++ b/src/email/README.md
@@ -32,6 +32,7 @@ const response = await ccai.email.sendSingle(
'john@example.com', // Email address
'Welcome to Our Service', // Subject
'
Hello John,
Welcome!
', // HTML message content
+ undefined, // textContent (optional plain-text alternative)
'noreply@yourcompany.com', // Sender email
'support@yourcompany.com', // Reply-to email
'Your Company', // Sender name
@@ -161,9 +162,7 @@ The `EmailOptions` type represents optional settings for email operations:
```typescript
type EmailOptions = {
- timeout?: number; // Optional timeout in milliseconds
- retries?: number; // Optional retry count for failed requests
- onProgress?: (status: string) => void; // Optional callback for tracking progress
+ onProgress?: (status: string) => void; // Callback for tracking send progress
};
```
diff --git a/src/email_send.ts b/src/email_send.ts
index c05e92d..bb0841d 100644
--- a/src/email_send.ts
+++ b/src/email_send.ts
@@ -14,6 +14,7 @@ async function sendEmail() {
'andreas@allcode.com',
'Test Email Subject',
'Hello ${firstName},
This is a test email.
Thanks,
AllCode Team
',
+ undefined, // textContent (optional plain-text alternative)
'noreply@allcode.com',
'support@allcode.com',
'AllCode Team',
diff --git a/src/examples/email-examples.ts b/src/examples/email-examples.ts
index 9bb9ff8..ce15e9c 100644
--- a/src/examples/email-examples.ts
+++ b/src/examples/email-examples.ts
@@ -22,6 +22,7 @@ async function sendSingleEmail() {
'andreas@allcode.com',
'Welcome to Our Service',
'Hello Andreas,
Thank you for signing up for our service!
Best regards,
AllCode Team
',
+ undefined, // textContent (optional plain-text alternative)
'noreply@allcode.com',
'support@allcode.com',
'AllCode',
@@ -184,6 +185,7 @@ async function sendHtmlTemplateEmail() {
'john@example.com',
'Welcome to Our Platform',
htmlTemplate,
+ undefined, // textContent (optional plain-text alternative)
'welcome@yourcompany.com',
'support@yourcompany.com',
'Your Company',
diff --git a/src/webhook/README.md b/src/webhook/README.md
index 9deb305..d4cf055 100644
--- a/src/webhook/README.md
+++ b/src/webhook/README.md
@@ -4,29 +4,46 @@ This module provides functionality for integrating with CloudContactAI's webhook
## Webhook Events
-CloudContactAI currently supports the following webhook events:
+CloudContactAI supports the following webhook event types, available via the `WebhookEventType` enum (`src/webhook/types.ts`):
-1. **Message Sent (Outbound)** - Triggered when a message is sent from your CloudContactAI account
-2. **Message Received (Inbound)** - Triggered when a message is received by your CloudContactAI account
+| Event | Value | Description |
+|---|---|---|
+| `MESSAGE_SENT` | `message.sent` | Outbound message sent from your account |
+| `MESSAGE_RECEIVED` | `message.received` | Inbound message received by your account |
+| `MESSAGE_INCOMING` | `message.incoming` | Incoming message before processing |
+| `MESSAGE_EXCLUDED` | `message.excluded` | Message excluded (e.g. opted-out contact) |
+| `MESSAGE_ERROR_CARRIER` | `message.error.carrier` | Carrier-side delivery error |
+| `MESSAGE_ERROR_CLOUDCONTACT` | `message.error.cloudcontact` | Platform-side delivery error |
+
+`createWebhookHandler` (see below) provides typed callbacks for `MESSAGE_SENT` and `MESSAGE_RECEIVED`. To handle the other four event types, call `ccai.webhook.parseEvent()` on the raw request body and switch on `eventType` yourself.
## Event Payload Schema
+Every event uses the same shape (`WebhookEvent` type in `src/webhook/types.ts`):
+
+```typescript
+type WebhookEvent = {
+ eventType: string; // e.g. "message.sent" | "message.received"
+ eventHash: string; // used for signature verification, see below
+ data: Record; // event-specific payload
+};
+```
+
### Message Sent Event
```json
{
- "type": "message.sent",
- "campaign": {
- "id": 123,
- "title": "Default Campaign",
- "message": "",
- "senderPhone": "+11234567894",
- "createdAt": "2025-07-14 22:18:28.273",
- "runAt": ""
- },
- "from": "+11234567894",
- "to": "+11453215437",
- "message": "this is a test message for Jon Doe"
+ "eventType": "message.sent",
+ "eventHash": "abc123def456ghi789",
+ "data": {
+ "To": "+15551234567",
+ "From": "+15551234567",
+ "Message": "Hello John, this is a test message",
+ "TotalPrice": "0.01",
+ "Segments": 1,
+ "CampaignId": "123",
+ "CampaignTitle": "Test Campaign"
+ }
}
```
@@ -34,43 +51,39 @@ CloudContactAI currently supports the following webhook events:
```json
{
- "type": "message.received",
- "campaign": {
- "id": 123,
- "title": "Default Campaign",
- "message": "",
- "senderPhone": "+11234567894",
- "createdAt": "2025-07-14 22:18:28.273",
- "runAt": ""
- },
- "from": "+11453215437",
- "to": "+11234567894",
- "message": "this is a reply message from Jon Doe"
+ "eventType": "message.received",
+ "eventHash": "xyz789abc123def456",
+ "data": {
+ "To": "+15551234567",
+ "From": "+15559876543",
+ "Message": "Reply from customer",
+ "TotalPrice": "0.01",
+ "Segments": 1,
+ "CampaignId": "123",
+ "CampaignTitle": "Test Campaign"
+ }
}
```
## Usage with Next.js
-The ccai-node library provides a convenient utility for handling webhooks in Next.js applications:
+The ccai-node library provides a convenient utility for handling webhooks in Next.js applications. `createWebhookHandler` does not verify the request signature itself — for that, use the manual handling pattern in [Manual Webhook Handling](#manual-webhook-handling) below.
```typescript
// pages/api/ccai-webhook.ts
import type { NextApiRequest, NextApiResponse } from 'next';
-import { createWebhookHandler, WebhookEventType } from 'ccai-node';
+import { createWebhookHandler, WebhookEventType, type WebhookEvent } from 'ccai-node';
export default createWebhookHandler({
- // Optional: Secret for verifying webhook signatures
- secret: process.env.CCAI_WEBHOOK_SECRET,
-
// Handler for outbound messages
- onMessageSent: async (event) => {
- console.log('Message sent:', event);
+ onMessageSent: async (event: WebhookEvent) => {
+ console.log('Message sent:', event.eventType, event.data);
// Process outbound message event
},
-
+
// Handler for inbound messages
- onMessageReceived: async (event) => {
- console.log('Message received:', event);
+ onMessageReceived: async (event: WebhookEvent) => {
+ console.log('Message received:', event.eventType, event.data);
// Process inbound message event
}
});
@@ -89,10 +102,6 @@ const ccai = new CCAI({
// Register a new webhook
const webhook = await ccai.webhook.register({
url: 'https://your-app.com/api/ccai-webhook',
- events: [
- WebhookEventType.MESSAGE_SENT,
- WebhookEventType.MESSAGE_RECEIVED
- ],
secret: 'your-webhook-secret' // Optional but recommended for security
});
```
@@ -101,25 +110,37 @@ const webhook = await ccai.webhook.register({
For production use, it's recommended to use a webhook secret to verify that webhook requests are coming from CloudContactAI. The secret is used to create a signature that is sent with each webhook request in the `X-CCAI-Signature` header.
-When you configure your webhook in the CloudContactAI interface (Settings -> Integrations), you can set a secret. This same secret should be used when setting up your webhook handler.
+When you configure your webhook in the CloudContactAI interface (Settings -> Integrations), you can set a secret. This same secret should be used when setting up your webhook handler, passed to `ccai.webhook.verifySignature(signature, clientId, eventHash, secret)`.
## Manual Webhook Handling
If you prefer to handle webhooks manually without using the provided utilities:
```typescript
+import { CCAI } from 'ccai-node';
+
+const ccai = new CCAI({
+ clientId: process.env.CCAI_CLIENT_ID!,
+ apiKey: process.env.CCAI_API_KEY!
+});
+
export default function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === 'POST') {
- const payload = req.body;
- console.log('Webhook payload:', payload);
-
- // Process the webhook based on its type
- if (payload.type === 'message.sent') {
+ const payload = req.body as { eventType: string; eventHash: string; data: Record };
+
+ const signature = req.headers['x-ccai-signature'] as string;
+ const secret = process.env.CCAI_WEBHOOK_SECRET!;
+ if (!ccai.webhook.verifySignature(signature, process.env.CCAI_CLIENT_ID!, payload.eventHash, secret)) {
+ return res.status(401).json({ error: 'Invalid signature' });
+ }
+
+ // Process the webhook based on its eventType
+ if (payload.eventType === 'message.sent') {
// Handle outbound message event
- } else if (payload.type === 'message.received') {
+ } else if (payload.eventType === 'message.received') {
// Handle inbound message event
}
-
+
// Always respond with a 200 status code to acknowledge receipt
res.status(200).json({ received: true });
} else {
diff --git a/test-webhook.js b/test-webhook.js
index fbf69da..21f57d8 100644
--- a/test-webhook.js
+++ b/test-webhook.js
@@ -2,18 +2,15 @@ const http = require('http');
// Test webhook payload
const testPayload = {
- type: 'message.sent',
- campaign: {
- id: 123,
- title: 'Test Campaign',
- message: '',
- senderPhone: '+11234567894',
- createdAt: '2025-01-14 22:18:28.273',
- runAt: ''
- },
- from: '+11234567894',
- to: '+15551234567',
- message: 'Hello John Doe, this is a test message!'
+ eventType: 'message.sent',
+ eventHash: 'test-event-hash-1234567890',
+ data: {
+ From: '+11234567894',
+ To: '+15551234567',
+ Message: 'Hello John Doe, this is a test message!',
+ CampaignId: '123',
+ CampaignTitle: 'Test Campaign'
+ }
};
const data = JSON.stringify(testPayload);
diff --git a/webhook-server.js b/webhook-server.js
index 9ccae9d..fc2c4ca 100644
--- a/webhook-server.js
+++ b/webhook-server.js
@@ -10,12 +10,12 @@ app.post('/webhook', (req, res) => {
const payload = req.body;
console.log('Webhook received:', payload);
-
+
// Handle different event types
- if (payload.type === 'message.sent') {
- console.log(`Message sent from ${payload.from} to ${payload.to}: ${payload.message}`);
- } else if (payload.type === 'message.received') {
- console.log(`Message received from ${payload.from} to ${payload.to}: ${payload.message}`);
+ if (payload.eventType === 'message.sent') {
+ console.log(`Message sent from ${payload.data?.From} to ${payload.data?.To}: ${payload.data?.Message}`);
+ } else if (payload.eventType === 'message.received') {
+ console.log(`Message received from ${payload.data?.From} to ${payload.data?.To}: ${payload.data?.Message}`);
}
// Always respond with 200