מפעילים קמפיינים קוליים מתוך הקוד שלכם: יוצרים טיוטה, מעלים הקלטה ואנשי קשר, ומפעילים. כל מה שהממשק עושה זמין גם דרך ה-API.
כל בקשה נושאת כותרת Authorization עם מפתח API שיוצרים במסך חשבון ← מפתחות API.
Authorization: Api-Key caster_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxהחליפו את $KEY במפתח שלכם ואת $CAMPAIGN_ID במזהה שהתקבל בשלב 2.
curl -H "Authorization: Api-Key $KEY" \
https://pulseem-dialer.com/api/campaignsהשדה caller_id (אופציונלי) חייב להיות מספר מזהה שאושר לחשבון שלכם.
curl -H "Authorization: Api-Key $KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Reminders Q1","retry_count":1}' \
https://pulseem-dialer.com/api/campaignscurl -H "Authorization: Api-Key $KEY" \
-F "file=@reminder.mp3" \
https://pulseem-dialer.com/api/campaigns/$CAMPAIGN_ID/audiocurl -H "Authorization: Api-Key $KEY" \
-F "file=@contacts.xlsx" \
https://pulseem-dialer.com/api/campaigns/$CAMPAIGN_ID/contacts/excelדורש מפתח עם הרשאת launch. לפי חוק התקשורת (תיקון 40, "חוק הספאם") חובה לאשר את ההצהרה ולציין מאיפה הגיעה ההסכמה של הנמענים. לפני ההפעלה המערכת מריצה בדיקת ציות, וציון נמוך חוסם את ההפעלה (ראו שגיאות).
curl -H "Authorization: Api-Key $KEY" \
-H "Content-Type: application/json" \
-d '{"anti_spam_ack":true,"consent_source":"signup form","consent_basis":"prior_consent"}' \
https://pulseem-dialer.com/api/campaigns/$CAMPAIGN_ID/launch| הרשאה | מה היא מאפשרת |
|---|---|
| read | קריאה בלבד. GET /campaigns, /campaigns/{id}, /campaigns/{id}/calls, /campaigns/{id}/report, /campaigns/{id}/report.csv, /lists |
| write | יצירה ועריכה, בלי הפעלה. POST /campaigns, PATCH /campaigns/{id}, POST /campaigns/{id}/audio, POST /campaigns/{id}/contacts/excel, /lists |
| launch | הפעלה ובקרה של קמפיינים. כולל גם את הרשאות write. POST /campaigns/{id}/launch, /pause, /resume, /cancel |
שגיאות חוזרות כ-JSON עם השדה detail: לפעמים מחרוזת, ולפעמים אובייקט בפורמט problem+json. במקרה של אובייקט, התיאור נמצא ב-detail.detail וקוד הסיבה (אם יש) ב-detail.code. בשגיאות אימות נתונים (422) detail הוא מערך של שדות.
{
"detail": {
"type": "about:blank",
"title": "Compliance blocked",
"status": 400,
"detail": "compliance_blocked",
"score": 22,
"failing_checks": [ ... ]
}
}401 מפתח חסר, שגוי, פג תוקף או בוטל.403 למפתח אין הרשאה לפעולה (למשל הפעלה בלי launch).409 הפעולה לא מתאימה למצב הקמפיין (למשל עריכה של קמפיין שכבר רץ).400 עם compliance_blocked או compliance_override_required: בדיקת הציות חסמה את ההפעלה.429 חריגה מהגבלת הקצב.עד 60 בקשות בדקה. מעבר לכך מתקבלת תשובת HTTP 429 עם כותרת Retry-After שמציינת בכמה שניות להמתין לפני ניסיון חוזר.
כל webhook נשלח עם הכותרת X-Caster-Signature בפורמט t=<unix-seconds>,v1=<hex>. הערך v1 הוא HMAC-SHA256 של המחרוזת ${t}.${rawBody} עם הסוד שמוצג פעם אחת ביצירת ה-webhook במסך התראות Webhook.
JSON.parse. כל שינוי ברווחים או בסדר השדות ישבור את האימות.import crypto from "node:crypto";
const TOLERANCE_SECONDS = 300;
// rawBody: the request body exactly as received (string or Buffer)
export function verifyCasterSignature(rawBody, header, secret) {
if (typeof header !== "string") return false;
const parts = Object.fromEntries(
header.split(",").map((piece) => piece.trim().split("=", 2)),
);
const t = Number(parts.t);
const v1 = parts.v1;
if (!Number.isInteger(t) || typeof v1 !== "string") return false;
if (Math.abs(Date.now() / 1000 - t) > TOLERANCE_SECONDS) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${t}.${rawBody}`)
.digest("hex");
const received = Buffer.from(v1, "hex");
const computed = Buffer.from(expected, "hex");
// timingSafeEqual throws on different lengths, so compare lengths first
return received.length === computed.length &&
crypto.timingSafeEqual(received, computed);
}
// Express: keep the raw body for this route
app.post("/webhooks/caster", express.raw({ type: "application/json" }), (req, res) => {
const ok = verifyCasterSignature(
req.body.toString("utf8"),
req.get("X-Caster-Signature"),
process.env.CASTER_WEBHOOK_SECRET,
);
if (!ok) return res.status(400).send("invalid signature");
const event = JSON.parse(req.body.toString("utf8"));
// ... handle event
res.sendStatus(200);
});import hashlib
import hmac
import time
from typing import Optional
TOLERANCE_SECONDS = 300
# header: request.headers.get("X-Caster-Signature") -- may be None
def verify_caster_signature(raw_body: bytes, header: Optional[str], secret: str) -> bool:
if not header:
return False
try:
parts = dict(piece.strip().split("=", 1) for piece in header.split(","))
t = int(parts["t"])
v1 = parts["v1"]
except (AttributeError, KeyError, TypeError, ValueError):
return False
if abs(time.time() - t) > TOLERANCE_SECONDS:
return False
signed = f"{t}.".encode() + raw_body
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
# compare bytes: compare_digest raises TypeError on non-ASCII str input
return hmac.compare_digest(expected.encode(), v1.encode())גוף הבקשה הוא אובייקט JSON של האירוע. אפשר להירשם ל-call.ended, dtmf.pressed, sms.delivered, whatsapp.delivered, campaign.launched ו-campaign.completed.
כדי לשייך תוצאה לרשומה במערכת שלכם (למשל CRM), העלו את אנשי הקשר עם עמודה בשם external_contact_id (או external_id) ושמרו אותה: בממשק בוחרים אותה במיפוי העמודות, ובקריאת API מציינים את מספר העמודה (מ-0) ב-metadata_cols של השדה column_overrides. הערך חוזר בשדה external_contact_id בכל אירוע של איש קשר (call.ended, dtmf.pressed, sms.delivered, whatsapp.delivered). כשלאיש הקשר אין ערך, השדה הוא null.
# contacts.csv: name,phone,external_contact_id
curl -H "Authorization: Api-Key $KEY" \
-F "file=@contacts.csv" \
-F 'column_overrides={"name":0,"phone":1,"metadata_cols":[2]}' \
https://pulseem-dialer.com/api/campaigns/$CAMPAIGN_ID/contacts/excel{
"event_type": "call.ended",
"campaign_id": "…",
"attempt_id": "…",
"contact_id": "…",
"outcome": "answered_full",
"listened_seconds": 27.4,
"listened_pct": 96.1,
"duration_seconds": 31.0,
"masked_phone": "+9725****0101",
"external_contact_id": "CRM-1042",
"retry_scheduled": false,
"at": "2026-10-01T09:15:02+00:00"
}השרת מפרסם מפרט OpenAPI 3 עדכני בכתובת https://pulseem-dialer.com/api/openapi.json. אפשר לייבא אותו ל-Postman או ל-Insomnia, או לייצר ממנו קליינט בכל שפה.