→ חזרה לאפליקציה

Shadran API

מפעילים קמפיינים קוליים מתוך הקוד שלכם: יוצרים טיוטה, מעלים הקלטה ואנשי קשר, ומפעילים. כל מה שהממשק עושה זמין גם דרך ה-API.

אימותAuthentication

כל בקשה נושאת כותרת Authorization עם מפתח API שיוצרים במסך חשבון ← מפתחות API.

Authorization: Api-Key caster_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  • המפתח המלא מוצג פעם אחת בלבד, ביצירה או בחידוש. שמרו אותו מיד במקום מאובטח.
  • כל מפתח משויך לחשבון שיצר אותו ורואה רק את הנתונים של החשבון הזה.
  • אל תשלבו מפתח בקוד שרץ בדפדפן או באפליקציה ציבורית. קריאות API מתבצעות מהשרת שלכם.

התחלה מהירהQuick start

החליפו את $KEY במפתח שלכם ואת $CAMPAIGN_ID במזהה שהתקבל בשלב 2.

1. רשימת הקמפיינים

curl -H "Authorization: Api-Key $KEY" \
  https://pulseem-dialer.com/api/campaigns

2. יצירת טיוטת קמפיין

השדה 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/campaigns

3. העלאת הקלטה

curl -H "Authorization: Api-Key $KEY" \
  -F "file=@reminder.mp3" \
  https://pulseem-dialer.com/api/campaigns/$CAMPAIGN_ID/audio

4. העלאת אנשי קשר (CSV או Excel)

curl -H "Authorization: Api-Key $KEY" \
  -F "file=@contacts.xlsx" \
  https://pulseem-dialer.com/api/campaigns/$CAMPAIGN_ID/contacts/excel

5. הפעלת הקמפיין

דורש מפתח עם הרשאת 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

הרשאותScopes

הרשאהמה היא מאפשרת
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

שגיאותErrors

שגיאות חוזרות כ-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 חריגה מהגבלת הקצב.

הגבלת קצבRate limits

עד 60 בקשות בדקה. מעבר לכך מתקבלת תשובת HTTP 429 עם כותרת Retry-After שמציינת בכמה שניות להמתין לפני ניסיון חוזר.

אימות חתימת WebhookWebhook signatures

כל webhook נשלח עם הכותרת X-Caster-Signature בפורמט t=<unix-seconds>,v1=<hex>. הערך v1 הוא HMAC-SHA256 של המחרוזת ${t}.${rawBody} עם הסוד שמוצג פעם אחת ביצירת ה-webhook במסך התראות Webhook.

  • חשבו את החתימה על גוף הבקשה הגולמי כפי שהתקבל, לפני JSON.parse. כל שינוי ברווחים או בסדר השדות ישבור את האימות.
  • דחו בקשות שחותמת הזמן שלהן רחוקה יותר מ-300 שניות מהשעון שלכם. כך מונעים שידור חוזר.
  • השוו חתימות בהשוואה בזמן קבוע (timingSafeEqual / compare_digest), לא עם ===.

Node.js

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);
});

Python

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"
}

מפרט OpenAPIOpenAPI

השרת מפרסם מפרט OpenAPI 3 עדכני בכתובת https://pulseem-dialer.com/api/openapi.json. אפשר לייבא אותו ל-Postman או ל-Insomnia, או לייצר ממנו קליינט בכל שפה.