← Tất cả bài viết
Hướng dẫn

Đưa tài liệu Webhook cho Devin Desktop, 20 phút có ngay handler nhận tin LINE

Devin Desktop — phiên bản đổi tên từ Windsurf đầu năm 2026, với model SWE-1.6 xuất ra 950 token/giây — là công cụ AI coding được các developer xung quanh tôi bàn tán nhiều nhất lúc này. Cách dùng rất đơn giản: đưa tài liệu tham khảo vào, mô tả cái bạn cần, lặp lại cho đến khi nó chạy được. Tôi muốn thử xem workflow này đi được bao xa khi tài liệu đầu vào là webhook API chứ không phải hướng dẫn framework.

Nhiệm vụ: viết server Python nhận tin nhắn LINE qua webhook event, xuất log có cấu trúc — loại handler mà team nhỏ sẽ deploy trước message queue hoặc hệ thống ticket. Không cần LINE Official Account, không cần Messaging API credentials, không cần quản lý channel access token. Chỉ cần tin nhắn đến ở dạng JSON chuẩn hóa.

Sản phẩm cuối cùng

Một server FastAPI (khoảng 40 dòng):

  1. Nhận event message.received từ webhook thống nhất của UnifyPort
  2. Xác thực mỗi delivery bằng HMAC-SHA256 với signing_secret
  3. Log mỗi tin nhắn dạng JSON có cấu trúc — provider, sender, text, timestamp
  4. Trả 200 cho mọi event, 401 khi chữ ký không hợp lệ

Thời gian: khoảng 20 phút. Bạn cần workspace UnifyPort với tài khoản LINE đã kết nối (quét QR từ app LINE) và Python 3.10+.

Sao không dùng thẳng LINE Messaging API?

Con đường chính thức:

  1. Tạo LINE Official Account — cần LINE Business ID, cần xác minh doanh nghiệp hoặc cá nhân
  2. Bật Messaging API trong LINE Developers console — cấu hình channel, tạo channel access token, đặt webhook URL
  3. Parse webhook payload của LINE: event có cấu trúc {"events": [{"type": "message", "message": {"type": "text", "text": "..."}, "source": {"userId": "U..."}}]} — dạng lồng nhau, riêng cho LINE
  4. Xác thực chữ ký bằng channel secret qua x-line-signature — LINE dùng HMAC-SHA256 mã hóa Base64, không phải hex
  5. Xử lý reply token: mỗi event kèm replyToken có hiệu lực 30 giây — quá hạn thì reply âm thầm thất bại

Code chạy được, nhưng chỉ chạy cho LINE. Định dạng webhook payload, format chữ ký, cơ chế reply token — không có gì tái sử dụng được cho WhatsApp hay Telegram. Thêm nền tảng thứ hai nghĩa là làm lại từ đầu.

Giao diện không chính thức của UnifyPort kết nối tài khoản LINE cá nhân — quét QR, không cần Official Account — và giao mỗi tin nhắn đến dưới dạng event message.received chuẩn hóa. Cùng format với WhatsApp, Telegram, TikTok, Zalo và X. Một handler phục vụ cả sáu nền tảng.

Chuẩn bị: đưa tài liệu API cho Devin Desktop

Mở Devin Desktop, tạo phiên làm việc mới. Trước prompt đầu tiên, cung cấp context:

  1. Dán payload event message.received từ tài liệu webhook UnifyPort:
    {
      "event": "message.received",
      "account_id": "acct_7kQnWx",
      "provider": "line",
      "from": "user_a3f82c",
      "text": "Dời sang thứ Sáu được không?",
      "timestamp": 1751270400,
      "message_id": "line_msg_5e9d21"
    }
  2. Dán phần xác thực HMAC-SHA256 — header x-unifyport-signature, tính hex digest trên raw request body bằng signing_secret

Devin index nội dung đã dán làm context phiên. SWE-1.6 Fast xử lý ở tốc độ 950 tok/s, tiêu hóa tài liệu chỉ mất vài giây. Cursor, Claude Code, Copilot cũng hoạt động tương tự — dán hoặc đính kèm. Công cụ thay đổi được; tài liệu mới là thứ quan trọng.

Quá trình build

Prompt đầu tiên — khung handler:

Dựa trên tài liệu webhook UnifyPort tôi vừa dán, viết server FastAPI với route POST /webhook. Khi event là “message.received”, log provider, from, text và timestamp dưới dạng JSON có cấu trúc bằng module logging Python. Trả 200 cho mọi event. Dùng uvicorn để chạy.

Devin đọc context và tạo ra:

from fastapi import FastAPI, Request
import logging, json

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("inbound")

app = FastAPI()

@app.post("/webhook")
async def webhook(request: Request):
    evt = await request.json()
    if evt.get("event") == "message.received":
        logger.info(json.dumps({
            "provider": evt["provider"],
            "from": evt["from"],
            "text": evt["text"],
            "timestamp": evt["timestamp"],
            "message_id": evt["message_id"],
        }))
    return {"status": "ok"}

12 dòng handler. Không import riêng cho LINE, không linebot SDK, không channel access token. Cấu trúc event đủ phẳng nên không cần parse thêm gì.

Prompt thứ hai — xác thực chữ ký:

Thêm xác thực chữ ký HMAC-SHA256. Header là x-unifyport-signature, secret từ biến môi trường UNIFYPORT_SIGNING_SECRET, HMAC tính trên raw request body bytes — không phải JSON đã serialize lại. Trả 401 nếu xác thực thất bại. Dùng so sánh timing-safe.

Prompt thứ ba — gộp và hoàn thiện:

Gộp xác thực chữ ký vào webhook handler. Parse body thành JSON sau khi xác thực thành công. Thêm GET /health. Thêm block uvicorn.run port 8000.

File server.py hoàn chỉnh:

from fastapi import FastAPI, Request, HTTPException
import hmac, hashlib, os, json, logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("inbound")

SIGNING_SECRET = os.environ["UNIFYPORT_SIGNING_SECRET"]

app = FastAPI()

@app.get("/health")
async def health():
    return {"status": "ok"}

@app.post("/webhook")
async def webhook(request: Request):
    body = await request.body()
    sig = request.headers.get("x-unifyport-signature", "")
    expected = hmac.new(
        SIGNING_SECRET.encode(), body, hashlib.sha256
    ).hexdigest()
    if not hmac.compare_digest(sig, expected):
        raise HTTPException(status_code=401, detail="invalid signature")

    evt = json.loads(body)
    if evt.get("event") == "message.received":
        logger.info(json.dumps({
            "provider": evt["provider"],
            "from": evt["from"],
            "text": evt["text"],
            "timestamp": evt["timestamp"],
            "message_id": evt["message_id"],
        }))
    return {"status": "ok"}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

Dưới 40 dòng. Xác thực chữ ký, log có cấu trúc, sẵn sàng deploy sau bất kỳ reverse proxy nào.

Chạy thử và xem tin LINE đến

Kết nối tài khoản LINE trong workspace UnifyPort — LINE hỗ trợ xác thực QR code, mở app LINE quét là xong trong vài giây. Không cần LINE Official Account, không cần Business ID, không cần cấu hình Messaging API.

Đăng ký webhook endpoint trỏ về server của bạn với subscribed_events: ["message.received"]signing_secret. Khởi động server:

UNIFYPORT_SIGNING_SECRET=your_secret python server.py

Nhờ bạn bè gửi tin test đến LINE của bạn. Event đến:

{
  "event": "message.received",
  "account_id": "acct_3Xk1wL",
  "provider": "line",
  "from": "user_a3f82c",
  "text": "Dời sang thứ Sáu được không?",
  "timestamp": 1751270400,
  "message_id": "line_msg_5e9d21"
}

Server xác thực chữ ký và xuất JSON có cấu trúc vào log. Nếu xác thực thất bại trả 401 — kiểm tra UNIFYPORT_SIGNING_SECRET có khớp với giá trị đặt trên webhook endpoint không.

Thêm WhatsApp mà không đổi một dòng code

Kết nối tài khoản WhatsApp trong cùng workspace, subscribe cùng webhook endpoint. Tin WhatsApp đến cùng format:

{
  "event": "message.received",
  "account_id": "acct_7kQnWx",
  "provider": "whatsapp",
  "from": "user_d4f29a",
  "text": "Đơn hàng xác nhận, gửi thứ Hai",
  "timestamp": 1751270460,
  "message_id": "wa_msg_8b3e71"
}

Cùng handler. Cùng log có cấu trúc. Trường provider đổi từ "line" sang "whatsapp", nhưng đường đi của code hoàn toàn giống nhau. Handler LINE Messaging API muốn thêm WhatsApp phải build tích hợp hoàn toàn riêng — webhook payload khác, format chữ ký khác, SDK khác. Handler này phục vụ sáu nền tảng vì cấu trúc event không đổi.

Ở Việt Nam, nơi cả Zalo lẫn LINE đều có mặt bên cạnh WhatsApp, thêm bất kỳ nền tảng nào vào hệ thống hiện có chỉ là thêm một giá trị provider trong payload — không thêm mô hình tính phí mới, không thêm SDK mới. Đưa tài liệu API UnifyPort vào Devin Desktop, mô tả handler, lặp đến khi chạy được. Thay Devin bằng Cursor, Claude Code hay Copilot đều được — tài liệu không đổi, handler dưới 40 dòng.