Outbound Leadopenapi.jsonคอนโซลพาร์ทเนอร์

Outbound Lead Partner API

เชื่อมระบบของคุณกับ Outbound Lead: สร้างธุรกิจให้ลูกค้า ตั้งค่าการหา lead จากกลุ่ม Facebook รับ lead ผ่าน webhook และดึงยอดใช้ไปคิดเงิน · Provision businesses, configure the Facebook lead scanner, receive leads by webhook and import usage for billing.

การยืนยันตัวตน · Authentication

ทุก request ส่ง Authorization: Bearer olp_… (key ยาว 52 ตัว ออกให้โดยทีม Outbound Lead แสดงครั้งเดียว) · Send your key on every request. Keys can expire (30/90/180/365 days or never); two keys may be active at once so you can rotate without downtime.

  • Base URL: https://outbound.mcloud.co.th/api/partner/v1
  • Rate limit: 240 requests / minute / key, 300 / minute / IP → 429 rate_limited + Retry-After
  • JSON only (Content-Type: application/json); unknown fields are refused (400) so typos surface early.
  • ทุก response มี X-Request-Id — แนบมาเมื่อติดต่อทีม · quote it when you contact us.
  • เวอร์ชัน v1 เปลี่ยนแบบเพิ่มเท่านั้น (additive): ฟิลด์ใหม่อาจเพิ่มขึ้น ห้ามพังเมื่อเจอฟิลด์ที่ไม่รู้จัก · ignore unknown fields.
  • ธุรกิจที่ไม่ใช่ของคุณตอบ 404 เสมอ · anything that is not yours is a 404.

ข้อผิดพลาด · Errors

{ "error": { "code": "validation_failed", "message": "The request is invalid. See `issues`.", "issues": [{ "path": "minScore", "message": "Too big: expected number to be <=5" }] } }
codeHTTPความหมายMeaning
validation_failed400ข้อมูลไม่ถูกต้อง ดู issuesInvalid input; see issues
missing_token401ไม่ได้ส่ง AuthorizationNo Bearer token
invalid_token401key ไม่ถูกต้องหรือถูกเพิกถอนUnknown or revoked key
token_disabled401key ถูกปิดชั่วคราวKey disabled
token_expired401key หมดอายุKey expired
partner_suspended403บัญชีพาร์ทเนอร์ถูกระงับPartner suspended
business_quota_exceeded403ธุรกิจครบโควตาBusiness quota reached
feature_disabled403ธุรกิจยังไม่เปิด Outbound LeadFeature off for the business
not_found404ไม่พบ หรือไม่ใช่ของพาร์ทเนอร์นี้Missing or not yours
conflict409ขัดกับสถานะปัจจุบัน (เช่น กลุ่มซ้ำ, หยุดสแกนอยู่)Conflicts with current state
idempotency_key_reused422ใช้ Idempotency-Key ซ้ำกับคำขอที่ต่างกันKey reused with a different request
rate_limited429เรียกถี่เกินไป รอตาม Retry-AfterSlow down; honour Retry-After
internal_error500ผิดพลาดฝั่งระบบ แจ้ง X-Request-IdServer error; quote X-Request-Id

ส่งซ้ำได้อย่างปลอดภัย · Idempotency

ใส่ Idempotency-Key (ข้อความใดก็ได้ ≤ 255 ตัว) กับทุก POST: ส่งซ้ำด้วย key และ body เดิมภายใน 24 ชม. จะได้ผลลัพธ์แรกกลับมาพร้อม Idempotent-Replayed: true โดยไม่ทำซ้ำ · key เดิมกับ body ต่างกัน → 422. Only successful (2xx) responses are replayed; an error releases the key.

POST /businesses ยัง idempotent ด้วย externalRef: ส่ง ref เดิมได้ธุรกิจเดิม (200) แทนที่จะสร้างใหม่ (201).

การแบ่งหน้า · Pagination

  • รายการทั่วไป: ?limit=&offset= → {items,total,limit,offset}
  • lead, usage records, activity, deliveries: ?cursor= → {items,nextCursor,hasMore}. Leads and usage records are oldest-first and return the cursor even on an empty page — store it and poll with it to receive only new rows.

เริ่มต้นใช้งาน · Quick start (curl)

# 0) who am I?
curl -s https://outbound.mcloud.co.th/api/partner/v1/whoami -H "Authorization: Bearer $OL_KEY"

# 1) create (or find) the business of your customer — idempotent on externalRef
curl -s -X POST https://outbound.mcloud.co.th/api/partner/v1/businesses \
  -H "Authorization: Bearer $OL_KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: create-tenant-42" \
  -d '{"name":"ร้านตัวอย่าง","externalRef":"tenant-42"}'

# 2) configure what to look for
curl -s -X PUT https://outbound.mcloud.co.th/api/partner/v1/businesses/$BID/settings \
  -H "Authorization: Bearer $OL_KEY" -H "Content-Type: application/json" \
  -d '{"keywords":["ขายฝาก","จำนอง"],"leadDefinition":"คนที่ต้องการเงินด่วนโดยมีที่ดินค้ำ","minScore":3,"dailyBudgetThb":200}'

# 3) the Facebook groups to read
curl -s -X POST https://outbound.mcloud.co.th/api/partner/v1/businesses/$BID/groups \
  -H "Authorization: Bearer $OL_KEY" -H "Content-Type: application/json" \
  -d '{"url":"https://www.facebook.com/groups/123456789","name":"กลุ่มขายฝาก"}'

# 4) switch the feature on (businesses start OFF)
curl -s -X PUT https://outbound.mcloud.co.th/api/partner/v1/businesses/$BID/features \
  -H "Authorization: Bearer $OL_KEY" -H "Content-Type: application/json" \
  -d '{"outboundLead":{"enabled":true}}'

# 5) read leads (keep nextCursor and poll with it), update their status
curl -s "https://outbound.mcloud.co.th/api/partner/v1/businesses/$BID/leads?limit=50&cursor=$CURSOR" -H "Authorization: Bearer $OL_KEY"
curl -s -X PATCH https://outbound.mcloud.co.th/api/partner/v1/businesses/$BID/leads/$LEAD_ID \
  -H "Authorization: Bearer $OL_KEY" -H "Content-Type: application/json" \
  -d '{"status":"contacting","note":"โทรแล้ว","partnerRef":"crm-77"}'

# 6) bill your customer: import usage rows (de-duplicate on id)
curl -s "https://outbound.mcloud.co.th/api/partner/v1/usage/records?cursor=$USAGE_CURSOR" -H "Authorization: Bearer $OL_KEY"

# 7) out of credit? pause scanning (no AI is spent while paused)
curl -s -X PUT https://outbound.mcloud.co.th/api/partner/v1/businesses/$BID/scanning \
  -H "Authorization: Bearer $OL_KEY" -H "Content-Type: application/json" \
  -d '{"paused":true,"reason":"credit exhausted"}'

Webhook · Webhooks

เพิ่มปลายทางด้วย POST /webhooks/endpoints (https เท่านั้น, ห้ามที่อยู่ภายใน) จะได้ secret ครั้งเดียว · Events: lead.created, lead.updated, scan.completed, bot.degraded, business.feature_changed. Answer 2xx within 10 s; failures are retried after 5 s, 30 s, 2 min, 10 min and 1 h, then marked dead (resend with POST /webhooks/deliveries/{id}/resend). Redirects are not followed.

POST https://your.app/webhooks/outbound-lead
Content-Type: application/json
X-OL-Event: lead.created
X-OL-Delivery: 0193f1c2-…            (one per attempt series)
X-OL-Signature: t=1761900000,v1=5f2c…  (HMAC-SHA256(secret, t + "." + rawBody), hex)

{
  "id": "0193f1c0-…",                 // event id — the same on every retry
  "type": "lead.created",
  "createdAt": "2026-10-31T09:12:00.000Z",
  "businessId": "0193e…",
  "data": { "lead": { "id": "…", "score": 4, "phone": "081…", "summary": "…", "postUrl": "…", … } }
}

ตรวจลายเซ็นทุกครั้ง · Always verify the signature

คำนวณ HMAC-SHA256 ของ t + "." + body ดิบ ด้วย secret แล้วเทียบกับ v1 แบบ constant-time และปฏิเสธ t ที่เก่ากว่า 5 นาที. During secret rotation the header may carry several v1 values — accept if any matches.

Node.js

import crypto from "node:crypto";
import express from "express";

const app = express();
const SECRET = process.env.OL_WEBHOOK_SECRET; // whsec_…

// IMPORTANT: verify the RAW body, before any JSON parsing
app.post("/webhooks/outbound-lead", express.raw({ type: "application/json" }), (req, res) => {
  const header = req.get("X-OL-Signature") ?? "";
  const parts = Object.fromEntries(header.split(",").map((p) => p.trim().split("=")));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > 300) return res.sendStatus(400); // replay window 5 min

  const expected = crypto.createHmac("sha256", SECRET).update(`${t}.${req.body}`).digest("hex");
  const ok = header.split(",").some((p) => {
    const [k, v] = p.trim().split("=");
    return k === "v1" && v.length === expected.length &&
      crypto.timingSafeEqual(Buffer.from(v, "hex"), Buffer.from(expected, "hex"));
  });
  if (!ok) return res.sendStatus(401);

  const event = JSON.parse(req.body.toString("utf8"));
  // de-duplicate on event.id (the same on every retry), then handle event.type
  res.sendStatus(200); // answer within 10 s; do slow work in the background
});

Python

import hashlib, hmac, json, os, time
from flask import Flask, request, abort

app = Flask(__name__)
SECRET = os.environ["OL_WEBHOOK_SECRET"].encode()  # whsec_…

@app.post("/webhooks/outbound-lead")
def outbound_lead():
    raw = request.get_data()  # the RAW body
    header = request.headers.get("X-OL-Signature", "")
    parts = [p.strip().split("=", 1) for p in header.split(",") if "=" in p]
    t = next((int(v) for k, v in parts if k == "t" and v.isdigit()), None)
    if t is None or abs(time.time() - t) > 300:
        abort(400)
    expected = hmac.new(SECRET, f"{t}.".encode() + raw, hashlib.sha256).hexdigest()
    if not any(k == "v1" and hmac.compare_digest(v, expected) for k, v in parts):
        abort(401)
    event = json.loads(raw)
    # de-duplicate on event["id"], then handle event["type"]
    return "", 200

รายการ endpoint · Endpoint reference

สร้างจาก openapi.json (OpenAPI 3.1) — นำเข้า Postman / Insomnia หรือสร้าง client ได้ทันที

กำลังโหลดรายการ endpoint…