← 全記事
チュートリアル

Zalo QR 認証: qrcode_expired の処理と署名付き Webhook 受信

Zalo の QR 認証で qrcode_expired が返ったら、古い QR を再利用しないでください。POST /v1/accounts/{account_id}/auth/qr/start をもう一度呼び、POST /v1/accounts/{account_id}/auth/qr/check のポーリングを続けます。Webhook endpoint は先に作成しておきます。認証状態と、その後の message.received イベントを受け取る場所が必要だからです。

要点

  • UnifyPort の Zalo 認証は QR-only です。auth_mode: "qrcode" の Zalo messaging account を作成し、provider credentials は事前に渡しません。
  • Webhook を最初に登録します。認証更新も入信メッセージも同じイベントストリームで届きます。
  • qrcode_expired は通常の再試行状態です。qr/start を再実行し、新しい QR を表示してからポーリングを続けます。
  • JSON を読む前に、X-Device-Timestamp + "." + raw bodyX-Device-Signature を検証します。
  • Zalo Official Account の ID や OA 機能が必須なら公式 OA ルートを評価してください。この記事は UnifyPort の非公式インターフェースで既存の messaging account を入信キューにつなぐ手順です。

日本のチームは LINE を中心に考えることが多いですが、ベトナム顧客向けには Zalo も同じサポート基盤へ入れたい場面があります。アカウントモデルを比較中なら Zalo Official Account API vs personal-account webhook を先に読み、複数チャネル設計は LINE、Zalo、X を 1 つの Webhook で扱う方法 と並べて確認してください。

このフローは公式 OA Webhook ではない

Zalo の公式開発者ドキュメントには Official Account API と OA Webhook があります。OA のブランド ID、OA Manager の運用、公式サポート関係が要件なら、そのモデルが適しています。

UnifyPort のフローは別物です。Zalo messaging account を QR 認証で接続し、UnifyPort の webhook delivery layer から標準化イベントを受け取ります。Zalo authorization は、Zalo が QR login を使うこと、そして認証・メッセージイベントを届けるために webhook endpoint が必要であることを示しています。

Step 1: スキャン前に署名付き Webhook を作成する

curl -X POST https://api.unifyport.ai/v1/webhook-endpoints \
  -H "X-Api-Key: $UNIFYPORT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://inbox.example.com/unifyport/zalo",
  "status": "active",
  "subscribed_events": ["account.auth.succeeded", "account.auth.required", "message.received"],
  "signing_secret": "zalo-support-2026"
}'

詳細は Create webhook endpoint にあります。url は absolute URL、statusactive または inactivesubscribed_events は公開イベント名または ["*"] を指定します。初回検証では明示的なイベント名のほうが原因を切り分けやすくなります。

Step 2: Zalo messaging account を作成する

curl -X POST https://api.unifyport.ai/v1/accounts \
  -H "X-Api-Key: $UNIFYPORT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Zalo Support Inbox",
  "provider": "zalo",
  "region": "global",
  "status": "active",
  "auth_mode": "qrcode",
  "capabilities": ["receive_message"],
  "provider_data": {},
  "metadata": { "workflow": "support-intake" }
}'

返された account_id を保存します。QR endpoints はこの値を使います。production の ID を公開チケットや AI ツールのプロンプトに貼らないでください。

Step 3: QR 認証を開始し、qrcode_expired を処理する

curl -X POST "https://api.unifyport.ai/v1/accounts/$ACCOUNT_ID/auth/qr/start" \
  -H "X-Api-Key: $UNIFYPORT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'

次にポーリングします。

curl -X POST "https://api.unifyport.ai/v1/accounts/$ACCOUNT_ID/auth/qr/check" \
  -H "X-Api-Key: $UNIFYPORT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'

qrcode_expired になったら古い QR を破棄し、qr/start を再実行します。管理画面は「スキャン待ち」「期限切れ、新しい QR を生成」「認証済み」の 3 状態に分けると運用しやすくなります。

Step 4: イベント処理前に署名を検証する

Webhook delivery の署名対象は次の文字列です。

<X-Device-Timestamp>.<raw request body>
import crypto from 'node:crypto';
import express from 'express';

const app = express();
const signingSecret = process.env.WEBHOOK_SIGNING_SECRET;

app.post('/unifyport/zalo', express.raw({ type: 'application/json' }), (req, res) => {
  const timestamp = req.get('X-Device-Timestamp') ?? '';
  const signature = req.get('X-Device-Signature') ?? '';

  const expected = crypto
    .createHmac('sha256', signingSecret)
    .update(timestamp + '.')
    .update(req.body)
    .digest('hex');

  if (signature.length !== expected.length) return res.sendStatus(401);
  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
    return res.sendStatus(401);
  }

  const event = JSON.parse(req.body.toString('utf8'));
  if (event.type === 'message.received' && event.provider === 'zalo') {
    // ルーティング前に event.id、event.account_id、event.occurred_at、event.data を保存する。
  }

  res.sendStatus(202);
});

Step 5: 標準化された Zalo イベントを保存する

{
  "id": "evt_2f9c1a4b7e",
  "type": "message.received",
  "provider": "zalo",
  "account_id": "acc_8c21d0",
  "occurred_at": "2026-06-08T12:34:56Z",
  "data": {
    "conversation": { "id": "5005", "type": "user" },
    "sender": { "id": "4004", "type": "user", "name": "Minh Nguyen" },
    "message": {
      "id": "3003",
      "text": "Sản phẩm này còn hàng không?",
      "direction": "inbound",
      "sent_at": "2026-06-08T12:34:55Z"
    }
  }
}

先に保存し、あとでルーティングします。Slack 通知、CRM 更新、AI 分類、担当者割り当ては durable に受け付けた後で実行します。一般的な受信設計は webhook-first inbound integration checklist も参考になります。

FAQ

Zalo が qrcode_expired を返したら?

auth/qr/start を再実行し、新しい QR を表示してから auth/qr/check のポーリングを続けます。期限切れ QR は使いません。

Zalo developer credentials は必要ですか?

この UnifyPort の Zalo QR flow では、provider credentials は事前に不要です。対象ユーザーが QR をスキャンした後にアカウント ID が確定します。

公式 Zalo OA Webhook と同じですか?

いいえ。公式 OA Webhook は Official Account 開発モデルです。この記事は UnifyPort の非公式インターフェースで標準化イベントを受け取る手順です。

次のステップ

Zalo authorizationWebhook delivery を並べて確認し、まずテストアカウントを接続して 1 通の Zalo 入信メッセージを受け取ってください。

Sources

Official sources checked on 2026-09-04:

UnifyPort API

メッセージ連携を安定したプロダクトパイプラインへ。

まずは 1 つの API で送信を始め、標準イベントですべての inbound メッセージを業務システムへ戻しましょう。