1. API Channel
Onebear
  • One Bear Public API
    • Webhooks
    • Getting started
    • Customers
      • List customers
      • Create a customer
      • Get a customer
      • Update a customer
    • Messages
      • List messages in a room
      • Get a message
    • Orders
      • List orders
      • Get an order
    • Products
      • List products
      • Get a product
    • Rooms
      • List rooms
      • Get a room
    • Schemas
      • CreateCustomerRequest
      • CustomerAddress
      • CustomerContactPerson
      • UpdateCustomerRequest
  • Live Chat Widget
    • Live Chat Widget - Setup & Integration Guide
    • OneBear Live Chat Widget — คู่มือตั้งค่าและการผสานระบบ
  • API Channel
    • API Channel - Setup & API Reference
    • OneBear API Channel — คู่มือตั้งค่าและ API Reference
  1. API Channel

API Channel - Setup & API Reference

How to connect an external system (a custom app, a LINE relay, a CRM, or any HTTP client) to OneBear so that messages flow through the same agent console, AI chatbot, auto-reply, assignment, and follow-up pipeline as every other channel.
The API Channel is a direct REST API channel — no OAuth, no iframe. You create a channel in the console, copy a Bearer key and a signing secret, then send messages by calling an HTTPS endpoint and receive agent replies on a webhook you supply.
Related: a runnable NestJS example lives in examples/api-channel-nestjs-relay/, and a Thai handoff checklist for the integration team in api-channel-handoff.th.md.

1. How it works#

Your external system
  │
  │  POST /api/v1/channels/{channelId}/messages          (inbound — you send)
  │  Authorization: Bearer sk_…
  ▼
OneBear API  ──▶  agent console (new or existing conversation)
  │                  auto-reply / AI chatbot / assignment / tags
  │
  │  POST https://your-webhook-url                       (outbound — we send)
  │  X-OneBear-Signature: t=…,v1=…
  ▼
Your external system  ──▶  deliver reply to end user
Round-trip (LINE-relay example):
LINE user sends message
  └─▶ your LINE webhook
        └─▶ POST /api/v1/channels/{channelId}/messages   externalUserId = LINE userId
              └─▶ OneBear: creates conversation, runs AI / auto-reply
                    └─▶ POST https://your-outbound-webhook  event:"message"
                          └─▶ call LINE push API to userId
Inbound messages are de-duplicated by clientMessageId (5-minute window), so it is safe to retry on network error.
Outbound replies carry senderType: "agent" (human) or "bot" (AI), so you know whether to suppress your own bot.
When the AI decides to hand off, OneBear fires event:"handoff" so your relay can stand down.
Messages flow through the same pipeline as every other channel — auto-reply, AI chatbot, room lifecycle (New → InProgress → Resolved), tags, and follow-ups all work.

2. Create the channel in the console#

1.
Sign in to OneBear → Settings → Integrations.
2.
Click Connect on API Channel.
3.
Fill in the settings:
FieldWhat it does
NameInternal label shown in the console inbox.
Outbound webhook URLHTTPS endpoint OneBear will POST agent replies to. Must be publicly reachable.
4.
Click Create channel. OneBear shows a one-time reveal:
ValueFormatShown
Channel ID (channelId)ac_…Always — visible on the detail page.
API keysk_…Once only — copy it now.
Signing secretwhsec_…Once only — copy it now.
Copy the API key and signing secret before closing this dialog. They are stored hashed and will never be shown again. You can rotate them later — rotating generates a new value and immediately revokes the old one.

3. Authentication#

Every call you make to the API Channel endpoints (ingest message, upload attachment, handoff, history) must include your API key as a Bearer token:
Authorization: Bearer sk_your_api_key
The key identifies which channel and company the request belongs to. The channelId in the URL is also verified against the key — a key for channel A cannot reach channel B even within the same company.
Key security:
Treat the key like a password. Store it in an environment variable or secret manager — never in source code.
To revoke a key, rotate it in the console (Settings → Integrations → your channel → Rotate API key). The new key is shown once; the old key stops working immediately.

4. Send an inbound message#

POST /api/v1/channels/{channelId}/messages
Authorization: Bearer sk_…
Content-Type: application/json
Request body:
{
    "externalUserId": "U1234567890abcdef",
    "displayName": "Alice",
    "pictureUrl": "https://cdn.example.com/u1.jpg",
    "content": "Hello, I need help with my order",
    "messageType": "text",
    "clientMessageId": "your-unique-idempotency-key",
    "timestamp": 1720000000000
}
FieldTypeRequiredDescription
externalUserIdstringYesStable identifier for the end user in your system (e.g. LINE userId, visitor id). OneBear creates one chat user per externalUserId + channel.
displayNamestringNoDisplay name shown in the console for a new user. Ignored if the user already exists.
contentstringConditionalMessage text. Required unless attachmentId is provided.
messageTypestringNo"text" (default), "image", or "file". When attachmentId is provided, type is derived from the attachment's content-type.
attachmentIdstringNoID returned by the attachment upload endpoint. Pass instead of (or alongside) content to send a file.
clientMessageIdstringYesCaller-assigned idempotency key. Duplicate values within 5 minutes return the original result without creating a duplicate message.
timestampintegerNoUnix millisecond timestamp (client time). Defaults to server time when omitted.
quoteTokenstringNoOpaque quote handle for this message (max 256 chars). Stored verbatim and echoed back in the outbound webhook when an agent quotes it.
replyToPlatformMessageIdstringNoSet when this message quotes an earlier one: the clientMessageId of the quoted message. Renders the quote in the agent console.

Quote / reply#

The reply affordance in the agent console is enabled for API channels. Two independent halves:
Inbound (customer quotes an agent): send replyToPlatformMessageId = the clientMessageId of the message being quoted. OneBear resolves it to a quote preview above the new message.
Outbound (agent quotes the customer): send a quoteToken on every inbound message. OneBear stores it against that message and hands the same string back in the outbound webhook when an agent replies to it — so you never have to maintain your own id map.
quoteToken is opaque to OneBear: any stable, reusable handle works. A relay fronting LINE should pass LINE's own quoteToken straight through.
When the customer quotes an agent reply: replyToPlatformMessageId must name an id OneBear knows. For outbound replies that is the placeholder OneBear minted (the messageId on the webhook), not your platform's own id. Report the real one with POST /api/v1/channels/{channelId}/messages/{messageId}/platform-id and body { "externalUserId": "...", "platformMessageId": "..." } — both values come straight off the webhook payload, so no conversation state is needed (best-effort, idempotent; a conversations/{conversationId}/… variant exists for callers that already track it) — or keep your own id map and send OneBear's messageId instead.
Shortcut: answer the webhook with 200 + Content-Type: application/json + body { "platformMessageId": "..." } and OneBear records it immediately — no separate call needed. An unusable body is skipped silently and never fails the delivery.
Do not send a LINE replyToken. It expires in ~30 seconds and is single-use, so it cannot survive the round trip through OneBear. LINE quoteTokens never expire and are reusable — that is the one you want.
200 response:
{
    "conversationId": "room_abc123",
    "messageId": "msg_xyz789",
    "isNewConversation": true,
    "replyToResolved": true
}
replyToResolved: true = the quote reference matched, false = it did not and the quote was dropped (the message itself was still delivered), absent = no replyToPlatformMessageId was sent. Use it to catch a broken quote reference without reading logs.
Error codes:
400 — missing required field, invalid format, or attachment not found.
403 — channelId in the URL does not match the authenticated key.
409 — ETag conflict (concurrent update on the same conversation — safe to retry).
429 — rate limit exceeded.
502 — transient downstream failure (safe to retry).

5. Send media (attachments)#

Upload the file first, then reference the returned ID in your ingest-message call.

Upload#

POST /api/v1/channels/{channelId}/attachments
Authorization: Bearer sk_…
Content-Type: multipart/form-data
Form field: file (the binary file)
Limits: 10 MB max. Accepted types: image/png, image/jpeg, image/gif, image/webp, application/pdf.
200 response:
{
    "id": "att_abc123",
    "fileName": "receipt.pdf",
    "contentType": "application/pdf",
    "size": 204800
}
Error codes: 400 no file, 413 file too large, 415 unsupported content type.

Send the attachment as a message#

POST /api/v1/channels/{channelId}/messages
{
  "externalUserId": "U1234567890",
  "attachmentId": "att_abc123",
  "clientMessageId": "msg-001"
}
messageType is derived automatically from the attachment's content-type ("image" for images, "file" for PDF and others).

6. Receive outbound replies (webhook)#

When an agent or the AI chatbot sends a reply, OneBear POSTs to your outbound webhook URL (the one you configured on the channel).
Request OneBear sends:
POST https://your-outbound-webhook.example.com/hook
Content-Type: application/json
X-OneBear-Signature: t=1720000000,v1=abc123def456…
Body (event: "message"):
{
    "event": "message",
    "externalUserId": "U1234567890abcdef",
    "messageId": "msg_xyz789",
    "content": "Hi Alice, your order #1234 has shipped!",
    "messageType": "text",
    "senderType": "agent",
    "senderName": "Alice Wong",
    "senderAvatarUrl": "https://…/member-avatars/…/abc.png",
    "mediaUrl": null,
    "fileName": null,
    "quoteToken": "the-quoteToken-you-sent-for-the-quoted-message",
    "timestamp": 1720000100000
}
Body (event: "handoff"):
{
    "event": "handoff",
    "conversationId": "room_abc123",
    "externalUserId": "U1234567890abcdef",
    "reason": "complex_query",
    "timestamp": 1720000200000
}
FieldDescription
event"message" — agent/AI reply; "handoff" — AI hands off to human.
externalUserIdThe same id you sent inbound. Use this to route the reply to the correct user.
conversationIdOneBear room id. Provided on handoff events; omitted on message (correlate by externalUserId).
messageIdPresent on message events. Use to fetch media if mediaUrl is set.
contentMessage text. May be null for media-only messages.
messageType"text" | "image" | "file"
senderType"agent" (human) | "bot" (AI chatbot). Use this to decide whether to label the reply.
senderNameDisplay name of the human agent who replied. Null on "bot" replies and when the agent has no name on file.
senderAvatarUrlAbsolute URL of that agent's profile photo — fetchable without your Bearer key. Null on "bot" replies and when the agent has no photo.
mediaUrlRelative URL to fetch the media: /api/v1/channels/{channelId}/media/{messageId}. Use your Bearer key to download it.
fileNameOriginal filename for file attachments.
quoteTokenPresent only when the agent/AI quoted an earlier customer message: the exact quoteToken you supplied for that message on ingest. Null otherwise.
timestampUnix millisecond timestamp.

Fetch media#

GET /api/v1/channels/{channelId}/media/{messageId}
Authorization: Bearer sk_…
Returns the file bytes with the appropriate Content-Type header. The endpoint enforces IDOR guards — the key can only fetch media from its own channel's conversations.

Verify the webhook signature#

OneBear signs every outbound webhook with your signing secret so you can confirm the request is genuine.
Header format: X-OneBear-Signature: t={unix_seconds},v1={lowercase_hex_hmac}
Algorithm: HMAC-SHA256(key=signingSecret, message="{t}.{rawBody}") where rawBody is the raw request body bytes as a string and t is the unix timestamp seconds from the header.
Always verify the signature and reject requests where t is more than 5 minutes old (replay protection).
Verification examples
Node.js
Python
PHP
C#
Important: always read the raw request body bytes before parsing JSON. Parsers may normalize whitespace, which changes the HMAC.

7. Handoff (AI → human)#

You can force a conversation off the AI and into the human-agent queue from your external system.

By external user id#

POST /api/v1/channels/{channelId}/handoff
Authorization: Bearer sk_…
Content-Type: application/json

{
  "externalUserId": "U1234567890abcdef",
  "reason": "complex_query",
  "note": "Customer says the order hasn't arrived after 3 weeks."
}

By conversation id#

POST /api/v1/channels/{channelId}/conversations/{conversationId}/handoff
Authorization: Bearer sk_…
Content-Type: application/json

{
  "reason": "human_requested"
}
FieldRequiredDescription
externalUserIdConditionalRequired if conversationId is not provided.
reasonNoFree-text reason label (e.g. "complex_query", "human_requested"). Defaults to "human_requested".
noteNoLonger note shown to the agent.
200 response:
{
    "conversationId": "room_abc123",
    "isAiMuted": true,
    "handoffSource": "api"
}
404 — conversation not found, or it does not belong to this channel.
Automatic handoff from OneBear: when OneBear's AI decides to hand off on its own, it sends event:"handoff" to your outbound webhook. Use this to stand down your relay's own bot.

8. Message history#

Retrieve paginated message history for a conversation:
GET /api/v1/channels/{channelId}/conversations/{conversationId}/messages?pageSize=50&continuationToken=
Authorization: Bearer sk_…
Returns messages newest-first. Pass the continuationToken from a response to fetch the next page.

9. Security#

ValueSensitivityWhere it may appear
Channel ID ac_…Not a secretYour config, logs, URLs — fine.
API key sk_…Secret (password-grade)Environment variable / secret manager only. Never in source code or client-side.
Signing secret whsec_…Secret (password-grade)Your webhook handler only. Never in source code or client-side.
The API key authenticates calls you make to OneBear.
The signing secret authenticates calls OneBear makes to you. Always verify the signature on incoming webhooks.
Your outbound webhook URL must be reachable over HTTPS. HTTP URLs and private/internal IPs are rejected.
The API key and signing secret are stored hashed and are never retrievable. Rotate to generate a new value and immediately revoke the old one.

10. Rotate keys#

In the console: Settings → Integrations → your channel → Rotate API key (or Rotate signing secret).
Rotation returns the new value once and immediately revokes the old one. Update your config before rotating to avoid a gap in service.

11. LINE-relay worked example#

This is the typical pattern for using the API Channel as a relay layer between LINE and OneBear.
Your LINE webhook handler:
Your OneBear outbound webhook:
Key observations:
externalUserId = LINE userId — this is the stable identifier that links both directions.
LINE message id used as clientMessageId gives safe retry on failure.
Check senderType to label messages as "agent" vs "bot" in your LINE chat.
Pass LINE's quoteToken (never replyToken) so agents can quote-reply; OneBear echoes it back verbatim.
On handoff, stop your relay bot from sending further bot-driven messages to that user.

12. Troubleshooting#

SymptomLikely cause / fix
401 UnauthorizedMissing or wrong Authorization: Bearer sk_… header. Confirm the key hasn't been rotated.
403 ForbiddenThe channelId in the URL does not match the channel the key belongs to. Check the URL.
400 VALIDATIONMissing externalUserId or clientMessageId, or invalid messageType. Check the error message field.
400 ATTACHMENT_NOT_FOUNDThe attachmentId doesn't exist or belongs to a different company. Upload it again with the same key.
413 on uploadFile exceeds 10 MB. Compress or split the file before uploading.
415 on uploadContent type not in the allowlist. Accepted: PNG, JPEG, GIF, WebP, PDF.
Agent quoted a message but no quote arrivedYou did not send a quoteToken on that inbound message, so there was nothing to echo back. The reply itself is still delivered — only the quote reference is missing.
Webhook not receivedYour outbound URL must be a public HTTPS endpoint. Check it is reachable from the internet. Verify in the console settings that the URL saved correctly.
Signature verification failsEnsure you pass the raw request bytes (before JSON parsing) to the HMAC. Check you're using the signing secret (whsec_…), not the API key.
Duplicate messages createdAdd clientMessageId (a stable, unique id per message event). Duplicates within 5 minutes are silently de-duped.
Old API key still rejected after rotationThe old key is revoked immediately on rotation. Update ONEBEAR_API_KEY in your config and redeploy.

13. FAQ#

Do I need the signing secret? Only to verify OneBear's outbound webhooks. If you don't have an outbound webhook URL, you don't need it. You can leave the webhook URL blank and poll the conversation history endpoint instead — but webhook-push is strongly recommended.
Can I have multiple API channels? Yes — each channel you connect gets its own channelId, API key, and signing secret, with separate inboxes in the console.
Can I use the same externalUserId across different channels? Users are scoped per channel. The same externalUserId value in two channels creates two separate chat users and conversations.
What happens if my webhook returns a non-2xx? OneBear retries up to 2 times on a 5xx, 429, or network/timeout error (the delivery client has a 10-second timeout and a circuit breaker); a 4xx is treated as terminal and is not retried. Because retries can re-deliver the same event, make your handler idempotent — de-duplicate by messageId. Return 200–299 quickly (within 10 seconds) and do heavy processing asynchronously.
Does AI auto-reply work on API channel messages? Yes — all normal eligibility checks apply (AI enabled for the channel, conversation state, etc.). The AI may hand off; listen for event:"handoff" on your webhook.
How do I test locally? Use a tunneling tool (e.g. ngrok) to expose your local webhook handler. Create a test channel in the DEV console, point its outbound URL at your ngrok tunnel, and use curl to POST test messages.
Modified at 2026-09-16 08:30:49
Previous
OneBear Live Chat Widget — คู่มือตั้งค่าและการผสานระบบ
Next
OneBear API Channel — คู่มือตั้งค่าและ API Reference
Built with