Messaging
Send and receive SMS and MMS on dedicated long code numbers. STOP and HELP handling, delivery receipts, and suppression lists are built in.
Stay compliant without extra workBuilt for high volume businesses
Cloud Communication Platform runs SMS, MMS, and voice on dedicated numbers for business. One REST API, isolated infrastructure for every account, and one straightforward low rate for everyone. No tiers, no contracts, no quotes to chase.
Why teams choose us
One straightforward low rate, the same whether you send a little or a lot. No tiers to climb and no haggling. The price you see is the price you pay.
One REST API for messaging, voice, numbers, and compliance, with code examples and webhooks. Your engineers ship in days, not weeks.
Every account runs in its own isolated environment with encrypted credentials, scoped API keys, and a full audit trail.
10DLC brand and campaign registration is set up and reviewed before you send, so your throughput stays high.
Month to month, cancel anytime. No commitments, no minimums, and no surprise fees. Just pay for what you use.
Every message and every completed call writes a ledger row. You can reconcile spend down to the cent.
Built for scale
The platform
One account, one API, and an in browser explorer. Each customer runs in an isolated environment, so your traffic and your numbers stay yours.
Send and receive SMS and MMS on dedicated long code numbers. STOP and HELP handling, delivery receipts, and suppression lists are built in.
Stay compliant without extra workPlace and receive calls, set up forwarding, and capture call status on the numbers you control. Calls are metered to the minute, rounded up.
Route and bill every call cleanlySearch, buy, and manage phone numbers from your dashboard. Every number you provision is tracked to your account, and nothing else is touched.
Full control of your number pool10DLC brand and campaign registration is walked through and reviewed before you go live, which keeps your messages landing.
Protect your deliverabilityEach message, inbound or outbound, and each completed call writes a ledger row. Every charge is there to audit.
No surprises at invoice timeA clean REST API with an in browser explorer. Try requests against live examples, then switch to your own numbers when you are ready.
Integrate in an afternoonNumber lookup intelligence
Validate and enrich any phone number in real time, so you stop paying to message landlines, dead numbers, and risky contacts. One lookup is a flat $0.0016, billed only for the numbers you check, one at a time or in bulk through the same API.
Mobile, landline, or VoIP. Drop landlines and unreachable numbers before they ever cost you a send.
The carrier the number is registered to, so you can route, segment, and reconcile your traffic by network.
Do Not Call registry status, so you suppress the numbers you should not contact and keep your lists compliant.
Whether the number belongs to a known TCPA litigator, so you can scrub serial litigators out before you ever message them.
Developers
One REST API for messaging, voice, numbers, and compliance. Authenticate with scoped per-account keys, try requests in the in browser explorer, then ship.
curl -X POST https://app2.cloudcommunicationplatform.com/v1/comms/messages \
-H "X-Tenant-ID: $CCP_TENANT" \
-H "X-Key-ID: $CCP_KEY_ID" \
-H "X-API-Key: $CCP_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "+15551234567",
"from": "+15550100100",
"body": "Your verification code is 4821"
}'
const res = await fetch(
"https://app2.cloudcommunicationplatform.com/v1/comms/messages",
{
method: "POST",
headers: {
"X-Tenant-ID": process.env.CCP_TENANT,
"X-Key-ID": process.env.CCP_KEY_ID,
"X-API-Key": process.env.CCP_KEY,
"Content-Type": "application/json"
},
body: JSON.stringify({
to: "+15551234567",
from: "+15550100100",
body: "Your verification code is 4821"
})
}
);
const message = await res.json();
import os, requests
res = requests.post(
"https://app2.cloudcommunicationplatform.com/v1/comms/messages",
headers={
"X-Tenant-ID": os.environ["CCP_TENANT"],
"X-Key-ID": os.environ["CCP_KEY_ID"],
"X-API-Key": os.environ["CCP_KEY"],
},
json={
"to": "+15551234567",
"from": "+15550100100",
"body": "Your verification code is 4821",
},
)
message = res.json()
package main
import (
"bytes"
"net/http"
"os"
)
func main() {
payload := []byte(`{
"to": "+15551234567",
"from": "+15550100100",
"body": "Your verification code is 4821"
}`)
req, _ := http.NewRequest(
"POST",
"https://app2.cloudcommunicationplatform.com/v1/comms/messages",
bytes.NewBuffer(payload),
)
req.Header.Set("X-Tenant-ID", os.Getenv("CCP_TENANT"))
req.Header.Set("X-Key-ID", os.Getenv("CCP_KEY_ID"))
req.Header.Set("X-API-Key", os.Getenv("CCP_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
}
<?php
$ch = curl_init("https://app2.cloudcommunicationplatform.com/v1/comms/messages");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"X-Tenant-ID: " . getenv("CCP_TENANT"),
"X-Key-ID: " . getenv("CCP_KEY_ID"),
"X-API-Key: " . getenv("CCP_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"to" => "+15551234567",
"from" => "+15550100100",
"body" => "Your verification code is 4821",
]),
]);
$message = json_decode(curl_exec($ch), true);
curl_close($ch);
{
"message": "Message accepted for delivery.",
"priority": false,
"status": "queued"
}
Real request and response shapes, with example values. Every call authenticates with three headers: X-Tenant-ID, X-Key-ID, and X-API-Key. A key can work across all of your accounts or be limited to one, and each key only has the permissions you give it.
POST /v1/comms/messages
Attach up to 10 media URLs to send a picture message from the same endpoint. Give the message a ref_id of up to 255 characters, such as your order or ticket number. The send is accepted and queued straight away, and the same ref_id comes back on every delivery receipt, so you can match receipts to your own records without storing ours.
priority to true for time sensitive messages such as passcodes. They go out ahead of the account's other queued messages.curl -X POST https://app2.cloudcommunicationplatform.com/v1/comms/messages \
-H "X-Tenant-ID: $CCP_TENANT" \
-H "X-Key-ID: $CCP_KEY_ID" \
-H "X-API-Key: $CCP_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "+15550100187",
"from": "+15550100142",
"body": "Your order is packed. Here is your receipt.",
"media_urls": ["https://example.com/receipts/10482.png"],
"ref_id": "order-10482",
"priority": false
}'
HTTP/1.1 202 Accepted
{
"message": "Message accepted for delivery.",
"priority": false,
"ref_id": "order-10482",
"status": "queued"
}
Every error comes back as JSON with an error field written for a person to read, and the HTTP status tells your code what kind of problem it is. A 400 means the request needs fixing. A 403 means the account is not allowed to do this yet, such as sending before its campaign is approved. Some errors add a code field your code can branch on.
HTTP/1.1 400 Bad Request
{
"error": "to and body (or media_urls) are required"
}
HTTP/1.1 403 Forbidden
{
"error": "this account is not registered to an approved 10DLC campaign yet"
}
GET /v1/comms/lookup
Pass any number in E.164 format and get back its line type, carrier, and Do Not Call status. Use it at the point of sign up to catch landlines and bad numbers before they reach your list.
curl -G https://app2.cloudcommunicationplatform.com/v1/comms/lookup \
-H "X-Tenant-ID: $CCP_TENANT" \
-H "X-Key-ID: $CCP_KEY_ID" \
-H "X-API-Key: $CCP_KEY" \
--data-urlencode "phone_number=+15550100187"
HTTP/1.1 200 OK
{
"carrier_name": "Example Wireless",
"dnc_type": "clean",
"line_type": "mobile",
"number": "+15550100187"
}
POST /v1/comms/lookup/bulk
Send up to 100 numbers in one request. The batch is accepted with a result_id. Poll it until status reads completed, then read every result in one response. Duplicates in the list are checked once.
curl -X POST https://app2.cloudcommunicationplatform.com/v1/comms/lookup/bulk \
-H "X-Tenant-ID: $CCP_TENANT" \
-H "X-Key-ID: $CCP_KEY_ID" \
-H "X-API-Key: $CCP_KEY" \
-H "Content-Type: application/json" \
-d '{
"numbers": ["+15550100187", "+15550100163", "+15550100121"]
}'
HTTP/1.1 202 Accepted
{
"result_id": "**********",
"total_unique": 3
}
GET https://app2.cloudcommunicationplatform.com/v1/comms/lookup/bulk/**********
HTTP/1.1 200 OK
{
"results": [
{
"carrier_name": "Example Wireless",
"dnc_type": "clean",
"line_type": "mobile",
"number": "+15550100187"
},
{
"carrier_name": "Example Telecom",
"dnc_type": "clean",
"line_type": "landline",
"number": "+15550100163"
},
{
"carrier_name": "Example Voice",
"dnc_type": "clean",
"line_type": "voip",
"number": "+15550100121"
}
],
"status": "completed"
}
PATCH /v1/comms/numbers/{id}/forwarding
Point a number's inbound calls at any phone. forward_caller_id decides what the person answering sees: caller shows who is calling, and number shows your business number. Leave voice_forward_to empty to stop forwarding.
curl -X PATCH https://app2.cloudcommunicationplatform.com/v1/comms/numbers/num_************************/forwarding \
-H "X-Tenant-ID: $CCP_TENANT" \
-H "X-Key-ID: $CCP_KEY_ID" \
-H "X-API-Key: $CCP_KEY" \
-H "Content-Type: application/json" \
-d '{
"voice_forward_to": "+15550100199",
"forward_caller_id": "caller"
}'
HTTP/1.1 200 OK
{
"status": "success"
}
Register an endpoint in your dashboard and events are pushed to it as they happen. You can also set a delivery address on an individual number. Those deliveries are signed the same way, using your account's relay secret from the dashboard.
message.inbound
Sent when someone texts one of your numbers, after the message is recorded and any STOP or START reply has been applied. num_media is the number of attachments. When it is above zero, a media_url field is added so you can fetch the attachments through the API.
POST /ccp/events HTTP/1.1
Content-Type: application/json
X-CCP-Event-Type: message.inbound
X-CCP-Timestamp: 2026-09-29T18:42:07Z
X-CCP-Signature: ****************************************************************
{
"body": "Yes, Thursday works for me",
"event_type": "message.inbound",
"from": "+15550100187",
"message_sid": "msg_************************",
"num_media": "0",
"tenant_id": "****-****-****",
"timestamp": "2026-09-29T18:42:07Z",
"to": "+15550100142"
}
message.status
Sent as each outbound message moves through sent, delivered, undelivered, or failed. The ref_id you gave when sending is included. When delivery fails, error_code is filled in and an error_message field explains it in plain words.
POST /ccp/events HTTP/1.1
Content-Type: application/json
X-CCP-Event-Type: message.status
X-CCP-Timestamp: 2026-09-29T18:40:55Z
X-CCP-Signature: ****************************************************************
{
"error_code": "",
"event_type": "message.status",
"from": "+15550100142",
"message_sid": "msg_************************",
"ref_id": "order-10482",
"status": "delivered",
"tenant_id": "****-****-****",
"timestamp": "2026-09-29T18:40:55Z",
"to": "+15550100187"
}
call.inbound
Sent the moment a call reaches one of your numbers, before it is forwarded or routed, so your system can log the caller or open their record while the phone is still ringing. A call.status event follows as the call progresses and ends, with its direction and duration in seconds.
POST /ccp/events HTTP/1.1
Content-Type: application/json
X-CCP-Event-Type: call.inbound
X-CCP-Timestamp: 2026-09-29T18:51:32Z
X-CCP-Signature: ****************************************************************
{
"call_sid": "res_************************",
"event_type": "call.inbound",
"from": "+15550100187",
"tenant_id": "****-****-****",
"timestamp": "2026-09-29T18:51:32Z",
"to": "+15550100142"
}
Each delivery is a JSON POST signed with the secret you get when you register the endpoint. X-CCP-Signature is the hex encoded HMAC-SHA256 of the raw request body using that secret. Compute it over the exact bytes you received, before parsing, and compare in constant time. The timestamp field is inside the signed body, so rejecting anything older than a few minutes stops an old delivery from being replayed.
message.status, to a family such as message.*, or to everything.num_media.import crypto from "node:crypto";
import express from "express";
const app = express();
const secret = process.env.CCP_WEBHOOK_SECRET;
// Keep the raw body: the signature covers the exact bytes sent.
app.post("/ccp/events", express.raw({ type: "application/json" }), (req, res) => {
const expected = crypto.createHmac("sha256", secret).update(req.body).digest("hex");
const given = req.get("X-CCP-Signature") || "";
const valid = given.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected));
if (!valid) return res.sendStatus(401);
const event = JSON.parse(req.body);
// The timestamp is inside the signed body, so it can be trusted.
if (Date.now() - Date.parse(event.timestamp) > 5 * 60 * 1000) {
return res.sendStatus(400);
}
switch (event.event_type) {
case "message.inbound": /* save the reply */ break;
case "message.status": /* match event.ref_id to your record */ break;
}
res.sendStatus(200);
});
import hashlib, hmac, json, os
from datetime import datetime, timezone
from flask import Flask, abort, request
app = Flask(__name__)
SECRET = os.environ["CCP_WEBHOOK_SECRET"].encode()
@app.post("/ccp/events")
def ccp_events():
raw = request.get_data() # the exact bytes that were signed
expected = hmac.new(SECRET, raw, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, request.headers.get("X-CCP-Signature", "")):
abort(401)
event = json.loads(raw)
sent = datetime.fromisoformat(event["timestamp"].replace("Z", "+00:00"))
if (datetime.now(timezone.utc) - sent).total_seconds() > 300:
abort(400)
if event["event_type"] == "message.status":
pass # match event.get("ref_id") to your record
return "", 200
func ccpEvents(w http.ResponseWriter, r *http.Request) {
raw, err := io.ReadAll(r.Body) // the exact bytes that were signed
if err != nil {
http.Error(w, "unreadable body", http.StatusBadRequest)
return
}
mac := hmac.New(sha256.New, []byte(os.Getenv("CCP_WEBHOOK_SECRET")))
mac.Write(raw)
expected := hex.EncodeToString(mac.Sum(nil))
if !hmac.Equal([]byte(expected), []byte(r.Header.Get("X-CCP-Signature"))) {
w.WriteHeader(http.StatusUnauthorized)
return
}
var event map[string]string // every value in the payload is a string
if err := json.Unmarshal(raw, &event); err != nil {
http.Error(w, "bad json", http.StatusBadRequest)
return
}
sent, err := time.Parse(time.RFC3339, event["timestamp"])
if err != nil || time.Since(sent) > 5*time.Minute {
w.WriteHeader(http.StatusBadRequest)
return
}
w.WriteHeader(http.StatusOK)
}
Use cases
From a few thousand messages a month to millions, the same platform carries it.
Automated reminders and confirmations that cut missed appointments and keep schedules full.
Deliver one time passcodes for sign in and high value actions, with checks that resolve in seconds.
Order updates, delivery tracking, payment notices, and service alerts that reach people right away.
Compliant marketing sends with consent, STOP, and HELP handled, so your campaigns stay clean.
Answer, route, and forward inbound calls, and capture status for every leg of the conversation.
Hold real conversations over SMS with customers, with the full thread written to your records.
Case studies
A look at the way various industries and operators put messaging and voice to work, and what one straightforward low rate tends to unlock.
A national retailer runs order, dispatch, and delivery notifications on dedicated numbers with campaign registration in place. Moving millions of messages a month onto our flat low rate cut the per message cost their old provider charged.
A multi clinic group sends appointment reminders and two way confirmations so patients can reply to confirm or reschedule. Fewer empty slots, plus a lower send rate, paid for the whole messaging program.
A payments app delivers one time passcodes and fraud alerts at sign in and checkout. Codes arrive in seconds, and consolidating verification onto one platform at our flat low rate dropped the cost per check.
Independent agents and small brokerages send listing alerts and follow up texts from their own numbers instead of personal phones. Automating the routine messages gave each agent hours back every week.
A hotel group handles booking confirmations, check in details, and guest requests over two way SMS. Pulling messaging out of a bundled tool and onto our flat low rate cut what they paid per guest.
A contractor network sends scheduling, dispatch, and on the way texts, plus voice routing to the right crew. Cutting missed and rescheduled jobs recovered real revenue each month on a modest send.
Transparent pricing
No tiers, no contracts, no quotes to chase. Every business gets the same straightforward best rate, with carrier fees already included. The rate you see is the rate you pay.
No sales call required. Create an account and start sending.
10DLC and compliance
10DLC is the system US carriers use to register businesses that send texts from ordinary 10 digit numbers. Every brand and every campaign has to be registered and approved before carriers will deliver the traffic. We guide you through it and review it with you first, because a clean first submission is the fastest route to sending.
Give your legal business name, EIN, address, website, and a contact. The registry checks these against government records, so they have to match your IRS filing exactly, punctuation included.
Choose the use case, such as marketing, account notifications, two factor codes, or customer care. Explain how people opt in, and give sample messages that look like the ones you will actually send.
We check your submission against the reasons reviewers most often reject and tell you what to fix first, because a rejection sends you to the back of the queue.
Brand checks often finish the same day. Campaigns are reviewed by hand, and approval takes 2 to 5 days on average.
Once the campaign is approved, attach up to 49 numbers to it and start sending at the throughput the carriers assign.
Most rejections come down to a handful of fixable problems:
Collect consent before the first message, and make it plain what the person is agreeing to. A compliant opt-in names your business, says what kind of messages they will get and roughly how often, notes that message and data rates may apply, and explains STOP and HELP. For marketing texts, consent cannot be a condition of buying anything, and it should be its own unchecked box or clear action.
By checking this box, you agree to receive recurring automated marketing and account texts from Acme Plumbing at the number provided. Consent is not a condition of purchase. Msg frequency varies. Msg & data rates may apply. Reply STOP to cancel, HELP for help. See our Privacy Policy and Terms.
Reviewers compare your samples with the use case you picked, so write them the way you will really send. Start with your business name, keep the content on purpose, and show opt-out instructions in at least one of them. Links should use your own domain rather than a public link shortener, since carriers filter shared shortener domains.
Acme Plumbing: Your appointment is confirmed for Tuesday between 1:00 PM and 3:00 PM. Reply C to confirm or R to reschedule. Reply STOP to opt out.
When someone replies STOP, CANCEL, END, QUIT, UNSUBSCRIBE, STOPALL, OPT-OUT, or REVOKE, they are opted out of that campaign at once and any later send to them is refused, so a stale list or a busy teammate cannot message them by mistake. A reply of START opts them back in, and HELP gets your support contact details. Every opt-out is kept on your suppression list, where you can see it.
Carriers restrict or refuse content about sex, hate, alcohol, firearms, and tobacco or vaping, often shortened to SHAFT. Cannabis and CBD, and high risk financial offers such as payday loans, debt relief, and third party lending, are also restricted for most senders. We screen outgoing messages for these categories so a problem is caught before it reaches a carrier and puts your campaign at risk.
Federal rules bar marketing texts before 8 AM or after 9 PM in the recipient's local time, and several states set a narrower window or limit how many messages a person can get in a day. Send when people expect to hear from you, at the frequency you promised when they signed up.
Carriers set how fast each campaign can send based on the brand's trust score and the use case. Accurate, consistent registration details are what earn a better score.
FAQ
Straight answers on registration, messaging, numbers, and calls. If yours is not here, ask it in the request form below.
Carriers deliver text in segments. A message written only in standard characters fits 160 characters in one segment. Anything longer is split into parts of 153 characters each, because a few characters in every part are used to put the message back together on the phone.
One emoji, curly quote, or special symbol switches the whole message to a different encoding that fits 70 characters per segment, or 67 per part once it is split. A plain 200 character message is 2 segments. The same message with one emoji in it is 3.
Yes. US carriers require every business texting from a standard 10 digit number to register its brand and each messaging campaign, and they block traffic that is not registered. We walk you through registration and review it with you before it is submitted.
On average, 10DLC approval takes 2 to 5 days. Brand checks often come back the same day, and campaigns are reviewed by hand. A submission that has to be corrected goes back into the queue, so one that is complete and consistent the first time is the fastest way through.
Replies of STOP, CANCEL, END, QUIT, UNSUBSCRIBE, STOPALL, OPT-OUT, or REVOKE opt that person out of the campaign right away. Any later send to them from that campaign is refused, so nobody on your team can message them by mistake. If they reply START, they are opted back in. HELP replies get your support contact details.
Yes. MMS runs on the same numbers as SMS, so you can send images and other media without a separate setup.
The caller's own number, so whoever answers can see who is calling. If you would rather show your business number, you can change that for each number in your dashboard. A caller who hides their number always shows up as your business number.
Not today. We do not offer number porting, so every number on the platform is one you pick and provision through your account.
US local 10 digit numbers with SMS, MMS, and voice. We do not offer toll free numbers or short codes.
Up to 49 numbers per campaign. If you need more, register another campaign and each new one starts with all 49 places open.
Each lookup returns the line type (mobile, landline, or VoIP), the carrier, Do Not Call registry status, and whether the number belongs to a known TCPA litigator. You can check one number at a time or a whole list through the API.
Not on pay as you go. That service is month to month and you can stop whenever you like. For higher volumes, a contract with an even lower rate is available. Talk to support to find out more.
Every account runs in its own isolated environment with its own encrypted credentials. API keys can be limited to the features each one needs, two factor sign in is required, and account activity is kept in a full audit trail.
Fill in the request form below. We review every new account so compliance is set up correctly from day one, then you register your brand and campaign, pick your numbers, and start sending once the campaign is approved.
Request access
We onboard new accounts by request so we can set up your compliance correctly from day one. Send the details below and we will follow up with next steps.