นักพัฒนาระบบ · API และ Webhook

ส่งข้อมูลสายเข้าระบบของคุณ

หลังจบทุกสาย AgenticOS มีเบอร์ผู้โทร สรุปการสนทนา ข้อมูลที่ AI เก็บได้ และบทสนทนาทั้งหมด ระบบของคุณดึงข้อมูลนี้ได้ด้วย API หรือให้เราส่งไปให้ทันทีผ่าน Webhook เช่น เข้า CRM, ERP หรือระบบ ticket
Base URL https://api.agenticos.tech/api/public/v1 · ข้อมูลใช้ snake_case และไม่เปลี่ยนชื่อฟิลด์เดิม (อาจเพิ่มฟิลด์ใหม่ได้)

1. ภาพรวม: เลือกแบบไหนดี

ต้องการใช้ตั้งค่าที่
ให้ระบบของเราได้ข้อมูลทันทีที่จบสายWebhook (call.ended)ตั้งค่า → API & การเชื่อมต่อ → Webhook
รู้ทันทีเมื่อมีสายเข้า หรือลูกค้าขอคุยกับคนระหว่างสายWebhook (call.started, call.handoff_requested) หรือแจ้งเตือน LINEตั้งค่า → API & การเชื่อมต่อ
ดึงย้อนหลัง หรือดึงเป็นรอบ (เช่น ทุกคืน)API (GET /voice/calls)ตั้งค่า → API & การเชื่อมต่อ → API key
ไฟล์เสียงของสายAPI (GET /voice/calls/{id} → recording_url)API key
ข้อมูลทั้งหมดขององค์กรเป็นไฟล์เดียวส่งออกเป็น ZIPตั้งค่า → API & การเชื่อมต่อ (เจ้าของบัญชี)
Webhook และ API ส่งข้อมูลของสายในรูปแบบเดียวกัน โค้ดฝั่งคุณจึงใช้ตัวแปลงข้อมูลชุดเดียวได้ทั้งสองทาง

2. สร้าง API key

  1. เข้าแอปด้วยบัญชีเจ้าของหรือแอดมินองค์กร แล้วไปที่ ตั้งค่า → API & การเชื่อมต่อ
  2. ในการ์ด API key กด "สร้าง key" ตั้งชื่อให้รู้ว่าใช้กับระบบไหน เช่น "CRM"
  3. คัดลอก key (ขึ้นต้นด้วย agos_) เก็บในเซิร์ฟเวอร์ของคุณทันที ระบบแสดง key เต็มครั้งเดียว เราเก็บแค่ค่า hash
  4. ถ้า key หลุด กด "ยกเลิก" แล้วสร้างใหม่ key เดิมใช้ไม่ได้ทันที

ใช้ key แยกกันต่อระบบ เพื่อยกเลิกได้ทีละระบบ และดูได้ว่าแต่ละ key ถูกใช้ล่าสุดเมื่อไร

3. การยืนยันสิทธิ์

ส่ง key ใน header แบบใดแบบหนึ่ง:

http
Authorization: Bearer agos_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
X-API-Key: agos_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
สถานะความหมาย
200สำเร็จ
401ไม่ได้ส่ง key, key ผิด หรือ key ถูกยกเลิกแล้ว
403key ไม่มีสิทธิ์ voice:read
404ไม่พบสายนี้ในองค์กรของ key
422พารามิเตอร์หรือ cursor ไม่ถูกต้อง

ข้อผิดพลาดตอบเป็น {"detail": "..."}

4. ดึงรายการสาย

GET/api/public/v1/voice/calls

สายขององค์กรเจ้าของ key เรียงจากใหม่ไปเก่า ไม่รวมสายที่กำลังคุยอยู่และสายที่ถูกลบ รายการนี้ไม่มีบทสนทนา (ดึงจากข้อ 5)

พารามิเตอร์ชนิดคำอธิบาย
sinceISO-8601เฉพาะสายที่สร้างตั้งแต่เวลานี้ เช่น 2026-10-01T00:00:00+07:00
untilISO-8601เฉพาะสายที่สร้างก่อนเวลานี้
directioninbound | outboundสายเข้า หรือสายออก
limit1–100จำนวนต่อหน้า ค่าเริ่มต้น 50
cursorstringค่า next_cursor จากหน้าก่อน เพื่อดึงหน้าถัดไป
bash
curl -s "https://api.agenticos.tech/api/public/v1/voice/calls?since=2026-10-01T00:00:00%2B07:00&limit=50" \
  -H "Authorization: Bearer $AGENTICOS_API_KEY"
json
{
  "data": [ { …call (section 6)… }, { … } ],
  "next_cursor": "WyIyMDI2LTEwLTA5VDA5OjI5OjIzIiwgIjEzMTVmODQzLi4uIl0="
}

ถ้า next_cursor เป็น null แปลว่าหมดแล้ว ถ้าดึงเป็นรอบ ให้จำเวลาของสายล่าสุดที่ได้ แล้วใช้เป็น since ของรอบถัดไป

5. ดึงสายเดียว พร้อมบทสนทนาและไฟล์เสียง

GET/api/public/v1/voice/calls/{id}

bash
curl -s "https://api.agenticos.tech/api/public/v1/voice/calls/1315f843-00df-4350-9d72-a29c0cdbd8f1" \
  -H "X-API-Key: $AGENTICOS_API_KEY"

ได้ข้อมูลสาย (ข้อ 6) บวกอีก 2 ฟิลด์:

json
{
  …call fields…,
  "transcript": [
    { "seq": 1, "who": "agent",    "at": "00:00", "text": "สวัสดีค่ะ ศูนย์บริการลูกค้า ยินดีให้บริการค่ะ" },
    { "seq": 2, "who": "customer", "at": "00:06", "text": "อยากได้กล่องลูกฟูกใส่ขนมครับ" }
  ],
  "recording_url": "https://…/call-1315f843….wav?X-Amz-…"
}
  • transcript ถอดจากไฟล์เสียงทั้งสายหลังวางสาย who เป็น customer หรือ agent ส่วน at คือเวลาตั้งแต่เริ่มสาย (นาที:วินาที)
  • recording_url เป็นลิงก์ดาวน์โหลดไฟล์ WAV ใช้ได้ 1 ชั่วโมง ถ้าต้องการเก็บ ให้ดาวน์โหลดไปเก็บเอง เป็น null ถ้าสายนั้นไม่มีไฟล์เสียง

6. ฟิลด์ของสาย

json
{
  "id": "1315f843-00df-4350-9d72-a29c0cdbd8f1",
  "direction": "inbound",
  "channel": "phone",
  "mode": "ai",
  "status": "completed",
  "phone": "0812345678",
  "caller": "0812345678",
  "agent": { "id": "eea31038-5836-4bd5-a941-bd98f5b86a2a", "name": "ศูนย์บริการลูกค้า (AI)" },
  "started_at": "2026-10-09T09:29:23.392000+00:00",
  "duration_sec": 95,
  "urgent": false,
  "summary": {
    "text": "ลูกค้าสอบถามกล่องลูกฟูกสำหรับใส่ขนม ประมาณ 5,000 ใบต่อเดือน และขอให้ฝ่ายขายติดต่อกลับ",
    "key_points": ["กล่องลูกฟูกใส่ขนม", "5,000 ใบต่อเดือน"],
    "outcome": "ส่งต่อฝ่ายขายกล่องลูกฟูก",
    "next_action": "ฝ่ายขายโทรกลับพร้อมใบเสนอราคา",
    "sentiment": "positive",
    "emotion": "สนใจ",
    "needs_human": false,
    "urgent_reason": ""
  },
  "collected": {
    "intent": "สอบถามสินค้า/ขอใบเสนอราคา",
    "customer_name": "สมชาย",
    "company": "เอบีซีฟู้ด",
    "callback_phone": "0812345678"
  },
  "topics": ["สอบถามราคา"],
  "tags": [],
  "has_recording": true,
  "app_url": "https://app.agenticos.tech/apps/voice-agent/calls/1315f843-00df-4350-9d72-a29c0cdbd8f1"
}
ฟิลด์คำอธิบาย
idรหัสสาย (ใช้กับ GET /voice/calls/{id})
directioninbound = ลูกค้าโทรเข้า, outbound = โทรออก
channelphone = โทรศัพท์, web = คุยผ่านปุ่มบนเว็บ
modeai = AI คุย, manual = พนักงานขายโทรเองผ่านผู้ช่วยโทรขาย
phoneเบอร์ผู้โทร (สายเข้า) หรือเบอร์ที่โทรไป (สายออก)
agentAI agent ที่คุยสายนี้ ({id, name}) หรือ null
started_at / duration_secเวลาเริ่มสาย (UTC) และความยาวเป็นวินาที
urgentAI ประเมินว่าลูกค้าต้องการคุยกับคน หรือเป็นเรื่องด่วน
summaryสรุปจาก AI: text, key_points, outcome, next_action, sentiment (positive/neutral/negative), emotion, needs_human, urgent_reason
collectedข้อมูลที่ AI เก็บได้ ชื่อฟิลด์ตรงกับ "ตัวแปรที่ต้องการเก็บ" ที่ตั้งใน agent ของคุณเอง เช่น customer_name, callback_phone
topicsหมวดเรื่องที่ลูกค้าโทรมา (AI จัดหมวดหลังวางสายไม่กี่นาที อาจยังว่างใน webhook)
has_recording / app_urlมีไฟล์เสียงไหม และลิงก์ไปหน้าสายนี้ในแอป

7. Webhook: ให้เราส่งข้อมูลไปหาคุณ

  1. เตรียม URL ฝั่งคุณที่รับ POST ได้ ต้องเป็น https และเข้าถึงได้จากอินเทอร์เน็ต
  2. ไปที่ ตั้งค่า → API & การเชื่อมต่อ → Webhook → เพิ่ม endpoint แล้วเลือกเหตุการณ์ที่ต้องการ
  3. คัดลอก signing secret (ขึ้นต้นด้วย whsec_) เก็บในเซิร์ฟเวอร์ ใช้ตรวจว่าข้อมูลมาจาก AgenticOS จริง (ข้อ 9) ระบบแสดงครั้งเดียว กด "เปลี่ยน secret" ได้ภายหลัง
  4. กด "ทดสอบส่ง" ระบบจะส่งเหตุการณ์ test ไปที่ URL ของคุณ ผลการส่งดูได้ในประวัติการส่งของ endpoint นั้น

แต่ละ endpoint มีสวิตช์เปิด/ปิด และเลือกเหตุการณ์ได้เอง องค์กรหนึ่งมีได้สูงสุด 5 endpoint

รูปแบบคำขอที่เราส่ง

http
POST https://your-server.example.com/agenticos/webhook
Content-Type: application/json
User-Agent: AgenticOS-Webhook/1.0
X-AgenticOS-Event: call.ended
X-AgenticOS-Delivery: 7b0c2b1e-4f7d-4a63-9d0e-2f4b8e1c9a10
X-AgenticOS-Signature: t=1760001234,v1=5f2b…c9

{
  "id": "7b0c2b1e-4f7d-4a63-9d0e-2f4b8e1c9a10",
  "event": "call.ended",
  "created_at": "2026-10-09T09:31:02.118000+00:00",
  "organization_id": "7dc951c1-9cbc-4571-b56a-cd2bd7979f44",
  "data": { … }
}

8. เหตุการณ์และข้อมูลใน data

เหตุการณ์ส่งเมื่อdata
call.startedมีสายเข้าถึง AI (โทรศัพท์หรือเว็บ){live_id, direction, channel, phone, agent, started_at}
call.handoff_requestedระหว่างสาย AI กำลังโอนสายให้คน เพราะลูกค้าขอคุยกับเจ้าหน้าที่ หรือผู้ดูแลกดโอน{live_id, channel, phone, agent}
call.endedบันทึกสายเสร็จ (มีสรุป ข้อมูลที่เก็บได้ และบทสนทนาแล้ว) ทั้งสายเข้าและสายออกที่ AI คุย{call: …call + transcript}
callpilot.endedสายที่พนักงานขายโทรเองผ่านผู้ช่วยโทรขายจบ (ต้องติ๊กเลือกเอง){call: …} mode = "manual"
testกดทดสอบส่งในหน้าตั้งค่า{message}

call.started

json
"data": {
  "live_id": "9eea211e4a484e3aaf416cf58fd96c8e",
  "direction": "inbound",
  "channel": "phone",
  "phone": "0812345678",
  "agent": { "id": "eea31038-…", "name": "ศูนย์บริการลูกค้า (AI)" },
  "started_at": "2026-10-09T09:29:23.392Z"
}

call.handoff_requested

json
"data": {
  "live_id": "9eea211e4a484e3aaf416cf58fd96c8e",
  "channel": "phone",
  "phone": "0812345678",
  "agent": { "id": "eea31038-…", "name": "ศูนย์บริการลูกค้า (AI)" }
}

live_id ใช้จับคู่ call.started กับ call.handoff_requested ของสายเดียวกัน ส่วน call.ended ใช้ id ของสายที่บันทึกแล้ว

call.ended / callpilot.ended

json
"data": {
  "call": {
    …every field in section 6…,
    "transcript": [ { "seq": 1, "who": "agent", "at": "00:00", "text": "…" } ]
  }
}
Webhook ไม่ส่งลิงก์ไฟล์เสียง เพราะลิงก์มีอายุสั้น ถ้าต้องการไฟล์เสียง ให้เรียก GET /voice/calls/{id} ด้วย id ที่ได้ สายของผู้ช่วยโทรขายอัปโหลดไฟล์เสียงหลังจบสายไม่กี่วินาที จึงอาจได้ has_recording: false ใน webhook

9. ตรวจลายเซ็น

ทุกคำขอมี header X-AgenticOS-Signature: t=<unix เวลา>,v1=<hex> โดย v1 = HMAC-SHA256(signing secret, "<t>.<body ดิบ>") ฝั่งคุณควร:

  • คำนวณจาก body ดิบก่อนแปลง JSON (ถ้าแปลงแล้วแปลงกลับ ลายเซ็นจะไม่ตรง)
  • เทียบด้วยฟังก์ชันแบบ constant-time และปฏิเสธถ้า t เก่ากว่า 5 นาที เพื่อกันการส่งซ้ำจากคนอื่น
  • ใช้ id (เท่ากับ X-AgenticOS-Delivery) กันประมวลผลซ้ำ เพราะเหตุการณ์เดิมอาจถูกส่งมากกว่า 1 ครั้งเมื่อมีการส่งซ้ำ

Node.js (Express)

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

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

// Read the RAW body — the signature is over the exact bytes we sent.
app.post("/agenticos/webhook", express.raw({ type: "application/json" }), (req, res) => {
  const header = req.get("X-AgenticOS-Signature") || "";          // t=1760000000,v1=ab12...
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const expected = crypto.createHmac("sha256", SECRET)
    .update(`${parts.t}.${req.body}`)
    .digest("hex");
  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;   // 5 min, against replays
  const ok = fresh && typeof parts.v1 === "string" && parts.v1.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
  if (!ok) return res.status(401).end();

  const event = JSON.parse(req.body);
  // event.id is unique per delivery — store it and skip repeats (retries).
  if (event.event === "call.ended") {
    const call = event.data.call;
    console.log(call.phone, call.summary.text, call.collected);
  }
  res.status(200).end();   // answer fast; do slow work in a queue
});

app.listen(3000);

Python (Flask)

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

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

@app.post("/agenticos/webhook")
def agenticos_webhook():
    raw = request.get_data()                      # exact bytes, before parsing
    parts = dict(p.split("=", 1) for p in request.headers.get("X-AgenticOS-Signature", "").split(",") if "=" in p)
    t = parts.get("t", "")
    expected = hmac.new(SECRET, f"{t}.".encode() + raw, hashlib.sha256).hexdigest()
    if not t.isdigit() or abs(time.time() - int(t)) > 300 or not hmac.compare_digest(expected, parts.get("v1", "")):
        abort(401)
    event = json.loads(raw)
    if event["event"] == "call.ended":
        call = event["data"]["call"]
        print(call["phone"], call["summary"]["text"], call["collected"])
    return "", 200

10. การตอบกลับและการส่งซ้ำ

  • ตอบ HTTP 2xx ภายใน 10 วินาที ถือว่ารับสำเร็จ ควรตอบก่อนแล้วค่อยประมวลผลต่อเบื้องหลัง
  • ถ้าตอบอย่างอื่น, ไม่ตอบ, หรือเชื่อมต่อไม่ได้ ระบบส่งซ้ำหลัง 1 นาที, 5 นาที, 30 นาที, 2 ชั่วโมง และ 6 ชั่วโมง (รวม 6 ครั้ง) แล้วหยุด
  • ระบบไม่ตาม redirect ถ้า URL เปลี่ยน ให้แก้ URL ในหน้าตั้งค่า
  • ประวัติการส่งย้อนหลัง 30 วัน ดูได้ต่อ endpoint ในหน้าตั้งค่า (สถานะ, HTTP code, ข้อผิดพลาด, ข้อมูลที่ส่ง)

11. แจ้งเตือนเข้า LINE

ถ้าไม่มีระบบรับ webhook ให้ทีมรับแจ้งเตือนทาง LINE ได้ ระบบส่งผ่าน LINE OA ขององค์กรที่เชื่อมไว้ใน OmniChat

  1. เชื่อม LINE OA ใน OmniChat ก่อน (ถ้ายังไม่ได้เชื่อม)
  2. ตั้งค่า → API & การเชื่อมต่อ → แจ้งเตือน LINE → "เชื่อม LINE" จะได้รหัส เช่น AGOS-123456 ใช้ได้ 15 นาที
  3. ส่งรหัสนั้นเข้า LINE OA จากแชทส่วนตัว หรือในกลุ่มที่เชิญ OA เข้าไปแล้ว แชทหรือกลุ่มนั้นจะได้รับแจ้งเตือน
  4. เลือกเปิด/ปิด: มีสายเข้า, ลูกค้าขอคุยกับเจ้าหน้าที่ระหว่างสาย, เรื่องด่วน (AI ประเมินหลังจบสาย), ทุกสายเข้าที่จบพร้อมสรุป

ข้อความแจ้งเตือนนับรวมในโควตาข้อความรายเดือนของ LINE OA

12. ขอข้อมูลทั้งหมดออกมา

เจ้าของบัญชีสั่งส่งออกข้อมูลทั้งหมดเป็นไฟล์ ZIP ได้ที่ ตั้งค่า → API & การเชื่อมต่อ ระบบส่งอีเมลแจ้งเมื่อไฟล์พร้อม ดาวน์โหลดได้ 7 วัน ในไฟล์มี:

โฟลเดอร์เนื้อหา
voice/ตั้งค่า agent, ประวัติการโทรพร้อมบทสนทนา สรุป และข้อมูลที่เก็บได้ (calls.jsonl รูปแบบเดียวกับ API และ calls.csv เปิดด้วย Excel ได้), ไฟล์เสียงของแต่ละสาย (เลือกไม่รวมได้)
chat/ช่องทางแชท (ไม่รวมรหัสผ่านหรือโทเคน), บทสนทนาพร้อมข้อความทั้งหมด, ไฟล์แนบ
knowledge/คลังความรู้, เนื้อหาเอกสารแต่ละฉบับ (.txt), ไฟล์ต้นฉบับที่อัปโหลด
README.txtคำอธิบายไฟล์และจำนวนข้อมูล

13. ข้อจำกัดและความปลอดภัย

  • API key และ signing secret ใช้ได้ในเซิร์ฟเวอร์เท่านั้น ห้ามใส่ในแอปมือถือหรือหน้าเว็บ
  • API ตอนนี้เป็นแบบอ่านอย่างเดียว (voice:read) ดึงได้ครั้งละไม่เกิน 100 สาย
  • Webhook ต้องเป็น https และ host ต้องเป็น IP สาธารณะ ระบบไม่ส่งเข้า IP ภายในหรือ localhost
  • สูงสุด 5 webhook endpoint ต่อองค์กร
  • ลิงก์ไฟล์เสียงใช้ได้ 1 ชั่วโมง และไฟล์เสียงในระบบถูกลบตามระยะเวลาจัดเก็บที่องค์กรตั้งไว้ (ตั้งค่า Voice Agent → PDPA)

ดู endpoint ทั้งหมดใน Swagger หัวข้อ public-api: api.agenticos.tech/docs

ต้องการความช่วยเหลือในการเชื่อมระบบ ติดต่อ [email protected]