Plivo is a cloud communications platform (CPaaS): programmable SMS, voice calls, and WhatsApp via API. It operates in 190+ countries and is popular as a more affordable alternative to Twilio - SMS and call rates are 30-50% lower with comparable coverage. For Kommo, a custom integration enables: sending SMS when a deal stage changes, logging inbound SMS into the deal card, and initiating outbound calls directly from Kommo.
Plivo uses a REST API with Basic Auth (Auth ID + Auth Token). Outbound SMS: POST /v1/Account/{auth_id}/Message/. Outbound calls: POST /v1/Account/{auth_id}/Call/ with an Answer URL that returns XML (Plivo Markup Language, PHML) containing call instructions.
Plivo Markup Language (PHML) is an XML format for controlling call behavior: play a message, connect to an agent, record the conversation. It is the equivalent of TwiML in Twilio.
Key Integration Scenarios
Scenario 1: When a deal moves to the “Send Proposal” stage - automatically send the client an SMS with a link to the proposal.
Scenario 2: Client sends an inbound SMS -> Plivo fires a webhook -> create a note in Kommo.
Scenario 3: A manager clicks “Call” in Kommo -> Plivo initiates the call through the browser or mobile device.
Implementation: Outbound SMS on Stage Change
import requests, os, hmac, hashlib
from flask import Flask, request, jsonify
app = Flask(__name__)
PLIVO_AUTH_ID = os.environ["PLIVO_AUTH_ID"]
PLIVO_AUTH_TOKEN = os.environ["PLIVO_AUTH_TOKEN"]
PLIVO_FROM_NUMBER = os.environ["PLIVO_FROM_NUMBER"] # e.g. +12025551234
KOMMO_SUBDOMAIN = os.environ["KOMMO_SUBDOMAIN"]
KOMMO_TOKEN = os.environ["KOMMO_ACCESS_TOKEN"]
KP_STAGE_ID = int(os.environ["KOMMO_KP_STAGE_ID"]) # "Send Proposal" stage
PLIVO_BASE = f"https://api.plivo.com/v1/Account/{PLIVO_AUTH_ID}"
PLIVO_AUTH = (PLIVO_AUTH_ID, PLIVO_AUTH_TOKEN)
KOMMO_BASE = f"https://{KOMMO_SUBDOMAIN}.kommo.com/api/v4"
KOMMO_HDR = {"Authorization": f"Bearer {KOMMO_TOKEN}", "Content-Type": "application/json"}
def get_contact_phone(lead_id: int) -> tuple[str, str]:
r = requests.get(
f"{KOMMO_BASE}/leads/{lead_id}",
headers=KOMMO_HDR,
params={"with": "contacts"},
)
contacts = r.json().get("_embedded", {}).get("contacts", [])
if not contacts:
return "", ""
contact_id = contacts[0]["id"]
rc = requests.get(
f"{KOMMO_BASE}/contacts/{contact_id}",
headers=KOMMO_HDR,
params={"with": "custom_fields_values"},
)
c = rc.json()
phone = ""
for cf in c.get("custom_fields_values", []) or []:
if cf.get("field_code") == "PHONE":
vals = cf.get("values", [])
if vals:
phone = vals[0].get("value", "")
break
return c.get("name", ""), phone
def send_sms(to: str, text: str) -> str:
# to: number in E.164 format: +12025551234
r = requests.post(
f"{PLIVO_BASE}/Message/",
auth=PLIVO_AUTH,
json={
"src": PLIVO_FROM_NUMBER,
"dst": to,
"text": text,
"type": "sms",
},
)
r.raise_for_status()
return r.json().get("message_uuid", [None])[0]
def add_note(lead_id: int, text: str):
requests.post(
f"{KOMMO_BASE}/notes",
headers=KOMMO_HDR,
json=[{
"entity_id": lead_id,
"entity_type": "leads",
"note_type": "common",
"params": {"text": text},
}],
)
@app.route("/webhooks/kommo", methods=["POST"])
def kommo_webhook():
data = request.json or {}
for lead_data in data.get("leads", {}).get("status", []):
lead_id = lead_data.get("id")
new_status = lead_data.get("status_id")
if new_status != KP_STAGE_ID:
continue
name, phone = get_contact_phone(lead_id)
if not phone:
continue
# Normalize phone number (simplified)
digits_only = "".join(c for c in phone if c.isdigit())
if not phone.startswith("+"):
phone = "+" + digits_only
sms_text = (
f"Hi {name.split()[0] if name else ''}! "
f"We're sending over our proposal. Reply to this message or give us a call."
)
msg_uuid = send_sms(phone, sms_text)
add_note(lead_id, f"Plivo SMS sent to {phone}. UUID: {msg_uuid}")
return jsonify({"status": "ok"}), 200
Implementation: Inbound SMS -> Kommo
def verify_plivo_signature(auth_id: str, auth_token: str, uri: str, params: dict, signature: str) -> bool:
# Plivo signs: auth_id + nonce (from X-Plivo-Nonce header) + URI
# Details: https://www.plivo.com/docs/sms/concepts/inbound-sms-webhooks/
# Simplified check via X-Plivo-Signature
sorted_params = "".join(f"{k}{v}" for k, v in sorted(params.items()))
msg = uri + sorted_params
digest = hmac.new(auth_token.encode(), msg.encode(), hashlib.sha1).digest()
import base64
computed = base64.b64encode(digest).decode()
return hmac.compare_digest(computed, signature)
@app.route("/webhooks/plivo/inbound-sms", methods=["POST"])
def plivo_inbound_sms():
params = request.form.to_dict()
from_number = params.get("From", "")
text = params.get("Text", "")
if not from_number or not text:
return "<?xml version="1.0" encoding="utf-8" ?><Response></Response>", 200
# Find the deal by phone number in Kommo
lead_id = find_lead_by_phone(from_number)
if lead_id:
add_note(lead_id, f"Inbound SMS from {from_number}: {text}")
# Plivo expects an XML response (PHML)
return "<?xml version="1.0" encoding="utf-8" ?><Response></Response>", 200
def find_lead_by_phone(phone: str) -> int | None:
# Search by phone number via Kommo Contacts API
r = requests.get(
f"{KOMMO_BASE}/contacts",
headers=KOMMO_HDR,
params={"query": phone, "limit": 5},
)
contacts = r.json().get("_embedded", {}).get("contacts", []) or []
if not contacts:
return None
contact_id = contacts[0]["id"]
# Find an open deal for this contact
r2 = requests.get(
f"{KOMMO_BASE}/leads",
headers=KOMMO_HDR,
params={"filter[contact_id]": contact_id, "filter[status_id]": "active", "limit": 1},
)
leads = r2.json().get("_embedded", {}).get("leads", []) or []
return leads[0]["id"] if leads else None
Implementation: Outbound Calls via Plivo
When a button is clicked in Kommo (via a custom action or manually triggered webhook), Plivo initiates a call: it first rings the agent, then connects them to the client.
ANSWER_URL = os.environ["PLIVO_ANSWER_URL"] # URL that serves the PHML
@app.route("/call/initiate", methods=["POST"])
def initiate_call():
data = request.json or {}
agent_phone = data.get("agent_phone") # agent's phone number
client_phone = data.get("client_phone") # client's phone number
lead_id = data.get("lead_id")
r = requests.post(
f"{PLIVO_BASE}/Call/",
auth=PLIVO_AUTH,
json={
"from": PLIVO_FROM_NUMBER,
"to": agent_phone, # ring the agent first
"answer_url": f"{ANSWER_URL}/phml/connect?client={client_phone}&lead={lead_id}",
"answer_method": "GET",
},
)
r.raise_for_status()
return jsonify({"call_uuid": r.json().get("request_uuid")}), 200
@app.route("/phml/connect", methods=["GET"])
def phml_connect():
client_phone = request.args.get("client", "")
lead_id = request.args.get("lead", "")
# PHML: once the agent answers, dial the client
phml = (
'<?xml version="1.0" encoding="utf-8" ?>
'
'<Response>
'
' <Speak>Connecting you to the client.</Speak>
'
f' <Dial callerId="{PLIVO_FROM_NUMBER}" record="true" recordingCallbackUrl="{ANSWER_URL}/webhooks/plivo/recording">
'
f' <Number>{client_phone}</Number>
'
' </Dial>
'
'</Response>'
)
return phml, 200, {"Content-Type": "application/xml"}
@app.route("/webhooks/plivo/recording", methods=["POST"])
def plivo_recording():
params = request.form.to_dict()
recording_url = params.get("RecordUrl", "")
lead_id = params.get("lead", "") # passed via callback URL parameter
if recording_url and lead_id:
add_note(int(lead_id), f"Plivo: call recording: {recording_url}")
return "", 200
Plivo Pricing
As of Q2 2026:
- SMS to the US: $0.0035/message (outbound)
- SMS to the UK: $0.04/message
- Calls to the US: $0.013/min (outbound)
- Phone number: $0.80/month
For comparison: Twilio - SMS $0.0079, calls $0.015/min.
Real-World Case
A real estate agency with 12 managers and 80 leads per month. Plivo was used to send property viewing reminders. Before the integration: SMS were sent manually. After: when a deal moves to the “Viewing Scheduled” stage, an SMS with the property address and appointment time is sent automatically. Time saved: 2 hours/week on routine messaging.
Who This Is For
Companies with high SMS volumes (50+ per month) in countries where Twilio rates are steep. Particularly relevant for agencies, service companies, and B2B SaaS with SMS onboarding flows. Plivo is most effective when you need to keep communication CAC low.
Similar integrations are covered for Kommo + Twilio and Kommo + Salesmsg.
Frequently Asked Questions
Is 10DLC registration required for SMS in the US via Plivo?
Yes. For A2P (Application-to-Person) SMS in the US, brand and campaign registration with The Campaign Registry (TCR) is required - the same applies to Twilio and other providers. Plivo provides registration tools through their Dashboard. Unregistered campaigns are blocked by carriers.
Does Plivo support WhatsApp?
Yes, via the Plivo WhatsApp API (in beta as of Q2 2026). It works through the official WhatsApp Business API and supports template and session messages. Production use requires Meta approval.
How do you handle delivery reports (SMS delivery status)?
Plivo sends DLR (Delivery Status Callback) to the URL specified in the url parameter when sending an SMS. Statuses: sent, delivered, failed. Add this URL in your API call and log the status as a note in Kommo.
Summary
Kommo + Plivo - programmable communications from your sales pipeline:
- Basic Auth (Auth ID + Auth Token), E.164 for phone numbers
- Kommo webhook -> Plivo SMS API -> log UUID as a note
- Inbound SMS: Plivo webhook -> find deal by phone -> add note
- Outbound calls:
POST /Call/-> PHML connect -> recording - 30-50% cheaper than Twilio, suitable for high volumes
If you need a Kommo integration with Plivo or another CPaaS provider - describe your requirements to the Exceltic.dev team.