CloudContactAI.CCAI.NET 1.5.0

dotnet add package CloudContactAI.CCAI.NET --version 1.5.0
                    
NuGet\Install-Package CloudContactAI.CCAI.NET -Version 1.5.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="CloudContactAI.CCAI.NET" Version="1.5.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="CloudContactAI.CCAI.NET" Version="1.5.0" />
                    
Directory.Packages.props
<PackageReference Include="CloudContactAI.CCAI.NET" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add CloudContactAI.CCAI.NET --version 1.5.0
                    
#r "nuget: CloudContactAI.CCAI.NET, 1.5.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package CloudContactAI.CCAI.NET@1.5.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=CloudContactAI.CCAI.NET&version=1.5.0
                    
Install as a Cake Addin
#tool nuget:?package=CloudContactAI.CCAI.NET&version=1.5.0
                    
Install as a Cake Tool

CCAI.NET

A C# client library for interacting with the CloudContactAI API.

Features

  • Send SMS messages to single or multiple recipients
  • Send MMS messages with images (automatic S3 upload)
  • Send Email campaigns to single or multiple recipients
  • Brand registration and management for TCR verification
  • Campaign registration and management for TCR carrier vetting
  • Manage contact opt-out preferences (SetDoNotText)
  • Validate email addresses (valid/invalid/risky) and phone numbers (valid/invalid/landline)
  • Webhook management: register, list, update, delete
  • Webhook signature verification
  • Template variable substitution (${firstName}, ${lastName})
  • Async/await support
  • Progress tracking
  • Comprehensive error handling
  • Full test coverage

Requirements

  • .NET 8.0 or higher

Installation

dotnet add package CloudContactAI.CCAI.NET
dotnet add package DotNetEnv

Configuration

Create a .env file in your project root:

CCAI_CLIENT_ID=1231
CCAI_API_KEY=your-api-key-here

Usage

SMS Basic Usage

using CCAI.NET;
using CCAI.NET.SMS;
using DotNetEnv;

// Load environment variables
Env.Load();

// Initialize the client
var config = new CCAIConfig
{
    ClientId = Environment.GetEnvironmentVariable("CCAI_CLIENT_ID") ?? throw new InvalidOperationException("CCAI_CLIENT_ID not found"),
    ApiKey = Environment.GetEnvironmentVariable("CCAI_API_KEY") ?? throw new InvalidOperationException("CCAI_API_KEY not found")
};

using var ccai = new CCAIClient(config);

// Send a single SMS
var response = await ccai.SMS.SendSingleAsync(
    firstName: "John",
    lastName: "Doe",
    phone: "+15551234567",
    message: "Hello ${FirstName}, this is a test message!",
    title: "Test Campaign"
);

Console.WriteLine($"Message sent with ID: {response.Id}");

// Send to multiple recipients
var accounts = new List<Account>
{
    new Account
    {
        FirstName = "John",
        LastName = "Doe",
        Phone = "+15551234567"
    },
    new Account
    {
        FirstName = "Jane",
        LastName = "Smith",
        Phone = "+15559876543"
    }
};

var campaignResponse = await ccai.SMS.SendAsync(
    accounts: accounts,
    message: "Hello ${FirstName} ${LastName}, this is a test message!",
    title: "Bulk Test Campaign"
);

Console.WriteLine($"Campaign sent with ID: {campaignResponse.CampaignId}");

MMS Usage

using CCAI.NET;
using CCAI.NET.SMS;
using DotNetEnv;

// Load environment variables
Env.Load();

// Initialize the client
var config = new CCAIConfig
{
    ClientId = Environment.GetEnvironmentVariable("CCAI_CLIENT_ID") ?? throw new InvalidOperationException("CCAI_CLIENT_ID not found"),
    ApiKey = Environment.GetEnvironmentVariable("CCAI_API_KEY") ?? throw new InvalidOperationException("CCAI_API_KEY not found")
};

using var ccai = new CCAIClient(config);

// Create options with progress tracking
var options = new SMSOptions
{
    Timeout = 60,
    OnProgress = status => Console.WriteLine($"Progress: {status}")
};

// Complete MMS workflow (get URL, upload image, send MMS)
var imagePath = "path/to/your/image.jpg";
var contentType = "image/jpeg";

// Define recipient
var account = new Account
{
    FirstName = "John",
    LastName = "Doe",
    Phone = "+15551234567"  // Use E.164 format
};

// Send MMS with image in one step
var response = await ccai.MMS.SendWithImageAsync(
    imagePath: imagePath,
    contentType: contentType,
    accounts: new[] { account },
    message: "Hello ${FirstName}, check out this image!",
    title: "MMS Campaign Example",
    options: options
);

Console.WriteLine($"MMS sent! Campaign ID: {response.CampaignId}");

Email Usage

using CCAI.NET;
using CCAI.NET.Email;
using DotNetEnv;

// Load environment variables
Env.Load();

// Initialize the client
var config = new CCAIConfig
{
    ClientId = Environment.GetEnvironmentVariable("CCAI_CLIENT_ID") ?? throw new InvalidOperationException("CCAI_CLIENT_ID not found"),
    ApiKey = Environment.GetEnvironmentVariable("CCAI_API_KEY") ?? throw new InvalidOperationException("CCAI_API_KEY not found")
};

using var ccai = new CCAIClient(config);

// Send a single email
var response = await ccai.Email.SendSingleAsync(
    firstName: "John",
    lastName: "Doe",
    email: "john@example.com",
    subject: "Welcome to Our Service",
    message: "<p>Hello ${FirstName},</p><p>Thank you for signing up!</p>",
    senderEmail: "noreply@yourcompany.com",
    replyEmail: "support@yourcompany.com",
    senderName: "Your Company",
    title: "Welcome Email"
);

Console.WriteLine($"Email sent with ID: {response.Id}");

// Send to multiple recipients
var emailAccounts = new List<EmailAccount>
{
    new EmailAccount
    {
        FirstName = "John",
        LastName = "Doe",
        Email = "john@example.com"
    },
    new EmailAccount
    {
        FirstName = "Jane",
        LastName = "Smith",
        Email = "jane@example.com"
    }
};

var campaign = new EmailCampaign
{
    Subject = "Monthly Newsletter",
    Title = "July 2025 Newsletter",
    Message = @"
        <h1>Monthly Newsletter - July 2025</h1>
        <p>Hello ${FirstName},</p>
        <p>Here are our updates for this month...</p>
    ",
    SenderEmail = "newsletter@yourcompany.com",
    ReplyEmail = "support@yourcompany.com",
    SenderName = "Your Company Newsletter",
    Accounts = emailAccounts,
    CampaignType = "EMAIL",
    AddToList = "noList",
    ContactInput = "accounts",
    FromType = "single",
    Senders = new List<object>()
};

var campaignResponse = await ccai.Email.SendCampaignAsync(
    campaign: campaign,
    options: new EmailOptions
    {
        OnProgress = status => Console.WriteLine($"Progress: {status}")
    }
);

Console.WriteLine($"Email campaign sent with ID: {campaignResponse.Id}");

Scheduled Email Campaign

// Schedule for tomorrow at 10:00 AM
var tomorrow = DateTime.Now.AddDays(1).Date.AddHours(10);

var scheduledCampaign = new EmailCampaign
{
    Subject = "Upcoming Event Reminder",
    Title = "Event Reminder Campaign",
    Message = @"
        <h1>Reminder: Upcoming Event</h1>
        <p>Hello ${FirstName},</p>
        <p>This is a reminder about our upcoming event tomorrow.</p>
    ",
    SenderEmail = "events@yourcompany.com",
    ReplyEmail = "events@yourcompany.com",
    SenderName = "Your Company Events",
    Accounts = emailAccounts,
    ScheduledTimestamp = tomorrow.ToString("o"), // ISO 8601 format
    ScheduledTimezone = "America/New_York"
};

var scheduledResponse = await ccai.Email.SendCampaignAsync(scheduledCampaign);
Console.WriteLine($"Email campaign scheduled with ID: {scheduledResponse.Id}");

Contact Management

Manage opt-out preferences for contacts.

using CCAI.NET;

var ccai = new CCAIClient(new CCAIConfig
{
    ClientId = "YOUR-CLIENT-ID",
    ApiKey = "YOUR-API-KEY"
});

// Opt a contact out of text messages (by phone number)
var result = await ccai.Contact.SetDoNotTextAsync(true, phone: "+15551234567");
Console.WriteLine($"Opted out: {result.Phone}, DoNotText={result.DoNotText}");

// Opt a contact back in
await ccai.Contact.SetDoNotTextAsync(false, phone: "+15551234567");

// Opt out by contactId
await ccai.Contact.SetDoNotTextAsync(true, contactId: "contact-abc-123");

Contact Validator

Validate email addresses and phone numbers.

Bulk endpoints accept up to 50 contacts per request and are processed server-side in chunks.

using CCAI.NET;
using CCAI.NET.ContactValidator;

// Validate a single email
var emailResult = await ccai.ContactValidator.ValidateEmailAsync("user@example.com");
Console.WriteLine(emailResult.Status); // "valid" | "invalid" | "risky"

// Validate multiple emails (up to 50, processed server-side in chunks)
var bulkEmails = await ccai.ContactValidator.ValidateEmailsAsync(new[] {
    "user@example.com",
    "bad@invalid.xyz"
});
Console.WriteLine(bulkEmails.Summary.Total); // 2
Console.WriteLine(bulkEmails.Summary.Valid);  // 1

// Validate a single phone number
var phoneResult = await ccai.ContactValidator.ValidatePhoneAsync("+15551234567", "US");
Console.WriteLine(phoneResult.Status); // "valid" | "invalid" | "landline"

// Validate multiple phone numbers (up to 50, processed server-side in chunks)
var bulkPhones = await ccai.ContactValidator.ValidatePhonesAsync(new[] {
    new PhoneInput { Phone = "+15551234567" },
    new PhoneInput { Phone = "+15559876543", CountryCode = "US" }
});
Console.WriteLine(bulkPhones.Summary.Landline); // 1

Webhook Management

CloudContact Webhook Events (New Format)

CloudContact now sends webhook notifications with a consistent structure for all event types:

using CCAI.NET;
using CCAI.NET.Webhook;

// Parse CloudContact webhook event
var cloudContactEvent = ccai.Webhook.ParseCloudContactEvent(json);

switch (cloudContactEvent.EventType)
{
    case "message.sent":
        Console.WriteLine($"✅ Message delivered to {cloudContactEvent.Data.To}");
        Console.WriteLine($"   Cost: ${cloudContactEvent.Data.TotalPrice}");
        Console.WriteLine($"   Segments: {cloudContactEvent.Data.Segments}");
        break;
        
    case "message.incoming":
        Console.WriteLine($"📨 Reply from {cloudContactEvent.Data.From}: {cloudContactEvent.Data.Message}");
        break;
        
    case "message.excluded":
        Console.WriteLine($"⚠️ Message excluded: {cloudContactEvent.Data.ExcludedReason}");
        break;
        
    case "message.error.carrier":
        Console.WriteLine($"❌ Carrier error {cloudContactEvent.Data.ErrorCode}: {cloudContactEvent.Data.ErrorMessage}");
        break;
        
    case "message.error.cloudcontact":
        Console.WriteLine($"🚨 System error {cloudContactEvent.Data.ErrorCode}: {cloudContactEvent.Data.ErrorMessage}");
        break;
}
Supported Event Types
  • message.sent - Message successfully delivered to recipient
  • message.incoming - Reply received from recipient
  • message.excluded - Message excluded during campaign (duplicates, invalid numbers, etc.)
  • message.error.carrier - Carrier-level delivery failure
  • message.error.cloudcontact - CloudContact system error
Webhook Endpoint Example
using CCAI.NET;
using CCAI.NET.Webhook;
using DotNetEnv;

// Load environment variables
Env.Load();

var config = new CCAIConfig
{
    ClientId = Environment.GetEnvironmentVariable("CCAI_CLIENT_ID") ?? throw new InvalidOperationException(),
    ApiKey = Environment.GetEnvironmentVariable("CCAI_API_KEY") ?? throw new InvalidOperationException()
};

using var ccai = new CCAIClient(config);

// In your webhook handler (e.g., ASP.NET Core controller):
public void HandleWebhook(string body, string signature)
{
    // Parse the webhook event
    var cloudContactEvent = ccai.Webhook.ParseCloudContactEvent(body);
    
    switch (cloudContactEvent.EventType)
    {
        case "message.sent":
            Console.WriteLine($"✅ Message delivered to {cloudContactEvent.Data.To}");
            break;
        case "message.incoming":
            Console.WriteLine($"📨 Reply from {cloudContactEvent.Data.From}");
            break;
        case "message.excluded":
            Console.WriteLine($"⚠️ Message excluded");
            break;
        // Handle other event types...
    }
}
Legacy Webhook Format (Backward Compatibility)

The library still supports the original webhook format:

using CCAI.NET;
using CCAI.NET.Webhook;
using DotNetEnv;

// Load environment variables
Env.Load();

// Initialize the client
var config = new CCAIConfig
{
    ClientId = Environment.GetEnvironmentVariable("CCAI_CLIENT_ID") ?? throw new InvalidOperationException("CCAI_CLIENT_ID not found"),
    ApiKey = Environment.GetEnvironmentVariable("CCAI_API_KEY") ?? throw new InvalidOperationException("CCAI_API_KEY not found")
};

using var ccai = new CCAIClient(config);

// Register a webhook
var webhookConfig = new WebhookConfig
{
    Url = "https://your-webhook-endpoint.com/webhook",
    Events = new List<WebhookEventType>
    {
        WebhookEventType.MessageSent,
        WebhookEventType.MessageIncoming,
        WebhookEventType.MessageExcluded,
        WebhookEventType.MessageErrorCarrier,
        WebhookEventType.MessageErrorCloudContact
    },
    Secret = "your-webhook-secret"
};

var registration = await ccai.Webhook.RegisterAsync(webhookConfig);
Console.WriteLine($"Webhook registered with ID: {registration.Id}");

// List all webhooks
var webhooks = await ccai.Webhook.ListAsync();
foreach (var webhook in webhooks)
{
    Console.WriteLine($"Webhook ID: {webhook.Id}, URL: {webhook.Url}");
}

// Update a webhook
var updatedConfig = new WebhookConfig
{
    Url = "https://your-updated-endpoint.com/webhook",
    Events = new List<WebhookEventType> { WebhookEventType.MessageSent },
    Secret = "your-updated-secret"
};

var updatedWebhook = await ccai.Webhook.UpdateAsync(registration.Id, updatedConfig);

// Delete a webhook
var deleteResponse = await ccai.Webhook.DeleteAsync(registration.Id);
Console.WriteLine($"Webhook deleted: {deleteResponse.Success}");

// Verify webhook signature (in your webhook handler)
var signature = request.headers['x-ccai-signature'];
var json = request.body;
var payload = JsonDocument.Parse(json);
var clientId = config.ClientId;
var eventHash = payload.RootElement.GetProperty("eventHash").GetString() ?? "";

if (ccai.Webhook.VerifySignature(signature, clientId, eventHash, webhookSecret))
{
    // Signature is valid, process the webhook
    var webhookEvent = ccai.Webhook.ParseEvent(json);
    
    if (webhookEvent is MessageSentEvent sentEvent)
    {
        Console.WriteLine($"Message sent to: {sentEvent.To}");
    }
    else if (webhookEvent is MessageIncomingEvent incomingEvent)
    {
        Console.WriteLine($"Message received from: {incomingEvent.From}");
    }
}
else
{
    Console.WriteLine("Invalid signature");
}

Brand Registration

Register and manage brands for TCR (The Campaign Registry) business verification.

using CCAI.NET;
using CCAI.NET.Brands;

var ccai = new CCAIClient(new CCAIConfig
{
    ClientId = "YOUR-CLIENT-ID",
    ApiKey = "YOUR-API-KEY"
});

// Create a brand
var brand = await ccai.Brands.CreateAsync(new BrandRequest
{
    LegalCompanyName = "Collect.org Inc.",
    Dba = "Collect",
    EntityType = "NON_PROFIT",
    TaxId = "123456789",
    TaxIdCountry = "US",
    Country = "US",
    VerticalType = "NON_PROFIT",
    WebsiteUrl = "https://www.collect.org",
    Street = "123 Main Street",
    City = "San Francisco",
    State = "CA",
    PostalCode = "94105",
    ContactFirstName = "Jane",
    ContactLastName = "Doe",
    ContactEmail = "jane@collect.org",
    ContactPhone = "+14155551234"
});
Console.WriteLine($"Brand created with ID: {brand.Id}");

// Get a brand by ID
var fetched = await ccai.Brands.GetAsync(brand.Id);
Console.WriteLine($"Website match score: {fetched.WebsiteMatchScore?.ToString() ?? "pending"}");

// List all brands for the account
var brands = await ccai.Brands.ListAsync();
Console.WriteLine($"Found {brands.Length} brand(s)");

// Update a brand (partial update)
var updated = await ccai.Brands.UpdateAsync(brand.Id, new BrandRequest
{
    Street = "456 Oak Avenue",
    City = "Los Angeles"
});

// Delete a brand
await ccai.Brands.DeleteAsync(brand.Id);
Entity Types

PRIVATE_PROFIT, PUBLIC_PROFIT, NON_PROFIT, GOVERNMENT, SOLE_PROPRIETOR

Note: PUBLIC_PROFIT entities require StockSymbol and StockExchange fields.

Vertical Types

AUTOMOTIVE, AGRICULTURE, BANKING, COMMUNICATION, CONSTRUCTION, EDUCATION, ENERGY, ENTERTAINMENT, GOVERNMENT, HEALTHCARE, HOSPITALITY, INSURANCE, LEGAL, MANUFACTURING, NON_PROFIT, PROFESSIONAL, REAL_ESTATE, RETAIL, TECHNOLOGY, TRANSPORTATION

Campaign Registration

Register and manage campaigns for TCR (The Campaign Registry) carrier vetting. Each campaign must be linked to a verified brand.

using CCAI.NET;
using CCAI.NET.Campaigns;

var ccai = new CCAIClient(new CCAIConfig
{
    ClientId = "YOUR-CLIENT-ID",
    ApiKey = "YOUR-API-KEY"
});

// Create a campaign
var campaign = await ccai.Campaigns.CreateAsync(new CampaignRequest
{
    BrandId = 1,
    UseCase = "MIXED",
    SubUseCases = new List<string> { "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",
    HasEmbeddedLinks = true,
    HasEmbeddedPhone = false,
    IsAgeGated = false,
    IsDirectLending = false,
    OptInKeywords = new List<string> { "START" },
    OptInMessage = "Welcome! Reply STOP to cancel.",
    OptInProofUrl = "https://example.com/opt-in-proof.png",
    HelpKeywords = new List<string> { "HELP" },
    HelpMessage = "For HELP email support@example.com.",
    OptOutKeywords = new List<string> { "STOP" },
    OptOutMessage = "STOP received. You are unsubscribed.",
    SampleMessages = new List<string>
    {
        "Your code is 554321. Reply STOP to cancel.",
        "Your ticket has been updated. Reply HELP for info."
    }
});
Console.WriteLine($"Campaign created with ID: {campaign.Id}");

// Get a campaign by ID
var fetched = await ccai.Campaigns.GetAsync(campaign.Id);

// List all campaigns for the account
var campaigns = await ccai.Campaigns.ListAsync();
Console.WriteLine($"Found {campaigns.Length} campaign(s)");

// Update a campaign (partial update)
var updated = await ccai.Campaigns.UpdateAsync(campaign.Id, new CampaignRequest
{
    Description = "Updated description."
});

// Delete a campaign
await ccai.Campaigns.DeleteAsync(campaign.Id);
Use Cases

TWO_FACTOR_AUTHENTICATION, ACCOUNT_NOTIFICATION, CUSTOMER_CARE, DELIVERY_NOTIFICATION, FRAUD_ALERT, HIGHER_EDUCATION, LOW_VOLUME_MIXED, MARKETING, MIXED, POLLING_VOTING, PUBLIC_SERVICE_ANNOUNCEMENT, SECURITY_ALERT

Note: MIXED and LOW_VOLUME_MIXED campaigns require 2–3 SubUseCases.

Sub-Use Cases

TWO_FACTOR_AUTHENTICATION, ACCOUNT_NOTIFICATION, CUSTOMER_CARE, DELIVERY_NOTIFICATION, FRAUD_ALERT, MARKETING, POLLING_VOTING

Step-by-Step MMS Workflow

// Step 1: Get a signed URL for uploading
var uploadResponse = await ccai.MMS.GetSignedUploadUrlAsync(
    fileName: "image.jpg",
    fileType: "image/jpeg"
);

var signedUrl = uploadResponse.SignedS3Url;
var fileKey = uploadResponse.FileKey;

// Step 2: Upload the image to the signed URL
var uploadSuccess = await ccai.MMS.UploadImageToSignedUrlAsync(
    signedUrl: signedUrl,
    filePath: "path/to/your/image.jpg",
    contentType: "image/jpeg"
);

if (uploadSuccess)
{
    // Step 3: Send the MMS with the uploaded image
    var response = await ccai.MMS.SendAsync(
        pictureFileKey: fileKey,
        accounts: accounts,
        message: "Hello ${FirstName}, check out this image!",
        title: "MMS Campaign Example"
    );
    
    Console.WriteLine($"MMS sent! Campaign ID: {response.CampaignId}");
}

With Progress Tracking

// Create options with progress tracking
var options = new SMSOptions
{
    Timeout = 60,
    Retries = 3,
    OnProgress = status => Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} - {status}")
};

// Send SMS with progress tracking
var response = await ccai.SMS.SendAsync(
    accounts: accounts,
    message: message,
    title: title,
    options: options
);

Synchronous API

// Send a single SMS synchronously
var response = ccai.SMS.SendSingle(
    firstName: "John",
    lastName: "Doe",
    phone: "+15551234567",
    message: "Hello ${FirstName}, this is a test message!",
    title: "Test Campaign"
);

// Send a single MMS synchronously
var mmsResponse = ccai.MMS.SendSingle(
    pictureFileKey: "your-client-id/campaign/image.jpg",
    firstName: "John",
    lastName: "Doe",
    phone: "+15551234567",
    message: "Hello ${FirstName}, check out this image!",
    title: "MMS Campaign"
);

// Send a single email synchronously
var emailResponse = ccai.Email.SendSingle(
    firstName: "John",
    lastName: "Doe",
    email: "john@example.com",
    subject: "Welcome to Our Service",
    message: "<p>Hello ${FirstName},</p><p>Thank you for signing up!</p>",
    senderEmail: "noreply@yourcompany.com",
    replyEmail: "support@yourcompany.com",
    senderName: "Your Company",
    title: "Welcome Email"
);

License

This project is licensed under the MIT License - see the LICENSE file for details.

Testing Webhook Installation

If you're testing the webhook installation, it's best to git clone the repository so you can get access to the examples/webhook-server project here

Step 1: Install Ngrok

brew install ngrok

Step 2: Verify Ngrok

ngrok version

Step 3: Start the standalone webhook server

Open a new terminal window and run:

cd /Users/../CCAI.NET/examples/webhook-server
dotnet run

This will start a webhook server on http://localhost:3000

Step 4: In another terminal, start ngrok

Open another terminal window and run:

ngrok http 3000

This will create a public tunnel to your local webhook server.

If you have not signed up for ngrok, you will need to:

ERROR: Sign up for an account: https://dashboard.ngrok.com/signup
ERROR: Install your authtoken: https://dashboard.ngrok.com/get-started/your-authtoken

Step 5: Get your ngrok URL

ngrok will display something like:

Forwarding    https://abc123.ngrok.io -> http://localhost:3000

Copy that https://abc123.ngrok.io URL - this is your public webhook URL.

Example: https://81dbae920588.ngrok-free.app

Step 6: Configure CCAI with your Ngrok URL

  1. Log in to your CCAI account
  2. Navigate to the Settings\Integration tab
  3. Specify your ngrok url + '/webhook'

Step 7: Send a test SMS to trigger webhook

cd /Users/../CCAI.NET/examples
dotnet run

Step 8: The Web server should receive the delivery notification

Press Ctrl+C to stop the server

Press Ctrl+C to stop the server

🔔 Received webhook event at root path! ⏰ Time: 2025-09-03 00:51:19 UTC 📋 Headers: Accept: application/json, application/*+json Host: 13c29ec4a161.ngrok-free.app User-Agent: Java/14-ea Accept-Encoding: gzip Content-Type: application/json Content-Length: 304 X-Forwarded-For: 159.65.99.19 X-Forwarded-Host: 13c29ec4a161.ngrok-free.app X-Forwarded-Proto: https 📄 Raw Body: {"eventType":"message.sent","data":{"id":142065,"MessageStatus":"SENT","To":"+14155551212","Message":"Hello John Doe, this is a test message!","CustomData":"","ClientExternalId":"a43c42c6-b0c1-45c5-b2fb-290ee7e6f113","CampaignId":141293,"CampaignTitle":"Default Campaign","Segments":1,"TotalPrice":0.03}}

🎯 Parsed CloudContact Event: Event Type: message.sent Message Status: SENT To: +14155551212 Message: Hello John Doe, this is a test message! ✅ Message delivered successfully! 💰 Cost: $0.0300 📊 Segments: 1 📢 Campaign: Default Campaign (ID: 141293) 🆔 External ID: a43c42c6-b0c1-45c5-b2fb-290ee7e6f113application/json;+charset=utf-8 137.6141ms


### Step 9: From your phone, respond to the message

On your mobile phone, respond to the message that was sent to you by CCAI

### Step 10: Web Server should receive the response notification

Received webhook event at /webhook path! Headers: Accept: text/plain, application/json, application/*+json, / Host: 81dbae920588.ngrok-free.app User-Agent: Java/14-ea Accept-Encoding: gzip Content-Type: application/json Content-Length: 204 X-Forwarded-For: 157.245.236.180 X-Forwarded-Host: 81dbae920588.ngrok-free.app X-Forwarded-Proto: https Body: {"campaign":{"id":141293,"title":"Default Campaign","message":"","senderPhone":null,"createdAt":"2025-08-13T21:20:50.212623Z","runAt":"null"},"from":"+1XXXYYYZZZZ","to":"+14158735045","message":"Rockin "} info: Microsoft.AspNetCore.Http.Result.OkObjectResult[1] Setting HTTP status code 200. info: Microsoft.AspNetCore.Http.Result.OkObjectResult[3] Writing value of type 'String' as Json. info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] Executed endpoint 'HTTP: POST /webhook' info: Microsoft.AspNetCore.Hosting.Diagnostics[2] Request finished HTTP/1.1 POST http://81dbae920588.ngrok-free.app/webhook - 200 - application/json;+charset=utf-8 3.7664ms


This demonstrates a complete webhook testing workflow where:
1. Outbound SMS messages trigger delivery notifications
2. Inbound SMS responses trigger message received notifications
3. All webhook events are captured and logged by your local webhook server
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.5.0 118 6/16/2026
1.4.5 254 10/1/2025
1.4.4 303 9/24/2025
1.4.3 199 9/5/2025
1.4.2 235 9/4/2025
1.4.1 227 9/3/2025
1.4.0 227 9/3/2025
1.3.0 261 8/27/2025
1.2.0 206 8/14/2025
1.1.0 218 8/14/2025
1.0.0 353 6/9/2025

# Release Notes - Version 1.5.0

## New Features
- Added `ContactValidator` service for validating email addresses and phone numbers
- `ValidateEmailAsync` / `ValidateEmail` — validate a single email, returns `valid`, `invalid`, or `risky` status with `safe_to_send` and `ai_verdict` metadata
- `ValidateEmailsAsync` / `ValidateEmails` — bulk email validation (up to 50 addresses) with summary counts
- `ValidatePhoneAsync` / `ValidatePhone` — validate a single phone number, returns `valid`, `invalid`, or `landline` status with carrier metadata
- `ValidatePhonesAsync` / `ValidatePhones` — bulk phone validation (up to 50 numbers) with summary counts including landline count
- New models: `EmailValidationResult`, `PhoneValidationResult`, `ValidationSummary`, `BulkEmailValidationResult`, `BulkPhoneValidationResult`, `PhoneInput`
- `IContactValidatorService` interface for dependency injection and testing