LINE Messaging API plugin: setup, config, and usage
Set up the official LINE plugin on your Gateway, which receives Messaging API webhooks using a channel access token and channel secret. Covers installation, configuration, and supported message types.
Read this when
- You want to connect OpenClaw to LINE
- You need LINE webhook + credential setup
- You want LINE-specific message options
LINE talks to OpenClaw through the LINE Messaging API. On the Gateway, the plugin acts as a webhook receiver, authenticating with your channel access token plus channel secret.
Status: an official plugin, installed on its own. Supported: direct messages, group chats, media, locations, Flex messages, template messages, and quick replies. Reactions and threads get no support.
Install
Before you configure the channel, install LINE:
openclaw plugins install @openclaw/line
Local checkout (for runs from a git repo):
openclaw plugins install ./path/to/local/line-plugin
Setup
- Sign up for a LINE Developers account, then open the Console: https://developers.line.biz/console/
- Add a Messaging API channel under a Provider you create or choose.
- From the channel settings, copy the Channel access token and Channel secret.
- In the Messaging API settings, turn on Use webhook.
- Point the webhook URL at your Gateway endpoint (HTTPS required):
https://gateway-host/line/webhook
LINE's signed webhook verification request gets a response from the Gateway: a POST whose events list is empty. Signed events arriving in LINE's standby mode are acknowledged with no queueing or reply, since chat control belongs to a different channel. Every other signed inbound event goes into the durable ingress queue ahead of 200. Agent processing then proceeds asynchronously.
Retries for failed delivery come from the queue, surviving a Gateway restart as well, and poison events turn into failed queue records once retries hit their bound. Should durable persistence break down, the request answers
500 rather than acknowledging an event that might be lost.
Across the queue-to-agent boundary, delivery happens at least once: if a Gateway shuts down or crashes mid-delivery, the turn can replay. Deduplication of message events uses the LINE message ID. For other event types, webhookEventId is used. Retained completion records suppress ordinary duplicate webhooks, yet handlers performing external side effects should remain idempotent regardless.
For a custom path, set channels.line.webhookPath or
channels.line.accounts.<id>.webhookPath and update the URL to match.
Security notes:
- Because LINE signature verification depends on the body (HMAC over the raw body), OpenClaw enforces a strict pre-auth body limit (64 KB) and read timeout ahead of verification.
- Webhook events are processed by OpenClaw from the verified raw request bytes. For signature-integrity safety, upstream middleware-transformed
req.bodyvalues are ignored.
Inbound durability
Per the Setup webhook contract, an event is acknowledged only once durably queued. The durable 200 carries x-openclaw-delivery-accepted: durable. Signed verification pings (empty event lists), standby-only batches, and error responses leave the marker out, which lets
reverse proxies require it to tell durable acceptance apart from a generic
200. Delivery then proceeds through the core channel-ingress drain, with settings specific to LINE:
- Per-conversation ordering. Source lane serializes events,
group:<groupId>,room:<roomId>, oruser:<userId>. An event lacking a conversation source gets its own event-scoped lane. Dispatch within a lane follows received order, so a retrying event holds up later events in that same chat. No chat's backlog can block another chat's lane, though every lane shares a cap of 8 concurrent deliveries: other chats keep progressing whenever a slot is free, and a 9th lane must wait for one to open. - Retries. Failed delivery retries on exponential backoff, beginning at 1 second and doubling each attempt, for roughly two minutes of cumulative backoff over the window. The event dead-letters (
retry-limit-exceeded) immediately after the 8th failed attempt: LINE opts out of the generic 24-hour dead-letter age floor, so a poison event cannot block its conversation lane for a whole day. - Non-retryable failures. Regardless of attempt count, these dead-letter at once with no further retries: stored payloads that no longer parse
(
invalid-event), deliveries whose side effects were already committed (delivery-side-effects-committed), and LINE API authentication failures (authentication-failed, HTTP 401/403). - Stall watchdog. A claimed delivery that for 5 minutes neither reaches agent-turn adoption nor reports continued deferred progress is aborted and released back to its lane under the same retry policy as any other failure: the event goes back to pending, its attempt count increments,
handler-timeoutis recorded as its last error, and it keeps its place at the head of its lane. A stall alone is not a dead-letter; only the retry limit above terminates the event, asretry-limit-exceeded. Coverage is limited to the window between claim and adoption: deferred progress re-arms the watchdog and adoption clears it, so a long agent turn never gets interrupted. Adoption arriving after the watchdog fires is fenced off, preventing a late turn from claiming an event already handed back. - Crash recovery. Each drain pass begins with a recovery sweep that reclaims any claim whose owning Gateway process has stopped running, so a delivery lost to a hard crash is retried on the next sweep instead of after a timeout. The 30-minute claim lease serves as the fallback bound for the reverse case: absent a successful lease refresh, it limits how long a claim stays protected purely because its owner PID still appears alive, including a reused PID whose process identity cannot be verified. Events accepted while the Gateway is stopping are still persisted and drain after the next start.
- Duplicate suppression window. At every admission, LINE drops completed and failed queue records older than 30 days, then retains the 4096 most recently updated records of each kind per account. Since pruning happens before the new event is queued rather than on a timer, records can survive past 30 days on an idle account, and a freshly completed record can push the count above 4096 until the next admission. While a record is present, a redelivered webhook for the same event is acknowledged without a second dispatch. Once the record is gone, through age or the cap, a redelivery is admitted and dispatched once more, so handlers with external side effects should not rely on this window in place of their own idempotency.
The 500-on-persistence-failure contract pays off only when LINE re-sends the event.
LINE redelivers a webhook if Webhook redelivery is enabled for the channel in
the LINE Developers Console (Messaging API settings, next to Use webhook) and
the bot server did not answer 2xx. Absent that setting, an event refused with
500 is never re-sent. Even with it enabled, redelivery is best effort, not a guarantee: LINE documents it as unreliable, with the retry count and interval undisclosed, and redelivered events may arrive out of order or more than once (the duplicate suppression window above absorbs the repeats). See
Redeliver a webhook that failed to be received.
Dead-lettered events remain inspectable and, depending on the failure reason, recoverable. See Inbound dead letters and Troubleshooting below.
Configure
Minimal config:
{
channels: {
line: {
enabled: true,
channelAccessToken: "LINE_CHANNEL_ACCESS_TOKEN",
channelSecret: "LINE_CHANNEL_SECRET",
dmPolicy: "pairing",
},
},
}
Public DM config:
{
channels: {
line: {
enabled: true,
channelAccessToken: "LINE_CHANNEL_ACCESS_TOKEN",
channelSecret: "LINE_CHANNEL_SECRET",
dmPolicy: "open",
allowFrom: ["*"],
},
},
}
Env vars (default account only):
LINE_CHANNEL_ACCESS_TOKENLINE_CHANNEL_SECRET
Token/secret files:
{
channels: {
line: {
tokenFile: "/path/to/line-token.txt",
secretFile: "/path/to/line-secret.txt",
},
},
}
tokenFile and secretFile must point to regular files. Symlinks are rejected.
Inline config values take precedence over files. Env vars are the final fallback for the default account.
Multiple accounts:
{
channels: {
line: {
accounts: {
marketing: {
channelAccessToken: "...",
channelSecret: "...",
webhookPath: "/line/marketing",
},
},
},
},
}
Access control
Direct messages default to pairing. Unknown senders receive a pairing code, and their messages are ignored until approved:
openclaw pairing list line
openclaw pairing approve line <CODE>
Allowlists and policies:
channels.line.dmPolicy:pairing | allowlist | open | disabled(defaultpairing)channels.line.allowFrom: LINE user IDs on the allowlist for DMs.dmPolicy: "open"needs["*"]channels.line.groupPolicy:allowlist | open | disabled(defaultallowlist)channels.line.groupAllowFrom: LINE user IDs on the allowlist for groups. Entries under DMallowFromdo not let group senders in- Per-group overrides:
channels.line.groups.<groupId>.allowFrom(along withenabled,requireMention,systemPrompt,skills). WhengroupPolicy: "allowlist"is set, configuregroupAllowFromor the per-groupallowFrom. Group messages stay blocked by an empty group allowlist, even if DMs are open. channels.line.groups."*"holds the defaults for all groups and rooms; it is not a fallback that a named entry swaps out. A named entry replaces"*"one field at a time, so any field the named entry leaves out comes from"*". This is consistent with the wayrequireMentionalready resolves through the shared group scope tree. See Groups.- Upgrade check: when
enabledorallowFromis set only onchannels.line.groups."*"and a named group or room is also listed, those wildcard values now reach that named entry too. In earlier releases the named entry was returned on its own, so fields defined only by wildcard never applied to it. Before you upgrade, inspect every"*"entry that setsenabled: falseor restrictsallowFrom, and restate the value on any named entry whose current access should be preserved. - A quote of one of the bot's own messages is treated as addressing it, so a group reply sent with LINE's quote gesture reaches the agent with no explicit mention. To prevent this from skipping the mention requirement, set
channels.defaults.implicitMentions.quotedBot: false. LINE reads that shared default and has no channel-scopedimplicitMentionsblock of its own. See Groups. The bot only recognizes a quote of its own message among the most recent messages it recalls sending (a few hundred per account), so quoting something older, or a message sent before the last Gateway restart, still requires a mention. allowFrom,groupAllowFrom, and per-groupallowFromwithaccessGroup:<name>can all reference static sender access groups. See Access groups.- Runtime note: when
channels.lineis entirely absent, runtime usesgroupPolicy="allowlist"for group checks instead (even ifchannels.defaults.groupPolicyis set).
LINE IDs are case-sensitive. Valid IDs look like:
- User:
U+ 32 hex chars - Group:
C+ 32 hex chars - Room:
R+ 32 hex chars
Directory
openclaw directory peers list --channel line lists user IDs drawn from the chosen
account's allowFrom, groupAllowFrom, and per-group allowFrom entries.
openclaw directory groups list --channel line lists the group and room IDs that are configured. Prefixes are normalized into sendable IDs, duplicates are shown once, and * and
accessGroup:<name> entries are left out. Use --account, --query, --limit,
and --json as described in Directory.
Both lists are built from configuration. They neither pull a live LINE contact roster nor include approvals kept through pairing.
Group join introductions
Once the bot is added to an allowed group or multi-person room, it posts a single introduction there. A group name is available from LINE's group summary API, but multi-person rooms expose no name or topic. Because the Messaging API cannot read earlier messages, introductions rely only on metadata that is available and ask what the room wants the bot to handle, instead of making up activity.
Introductions come enabled out of the box. Turn them off with channels.line.joinIntro: false, or target a single account using channels.line.accounts.<accountId>.joinIntro. They never fire in one-to-one user chats, nor when someone else joins. Room admission, the once-per-room rule, and the no-tools turn that treats room content as untrusted are covered under group join introductions.
Message behavior
- Text gets split into 5000-character chunks.
- Markdown formatting is removed. Where possible, code blocks and tables become Flex cards.
- Streaming responses get buffered, so LINE receives whole chunks. The loading animation appears only in one-to-one chats, because LINE's loading API takes a user id and turns down group and room ids, which means a group reply shows up without it. Heartbeat turns also display the loading animation while the reply is being generated.
- Media downloads are limited by
channels.line.mediaMaxMb(10 by default). - Before inbound media is handed to the agent it is stored under
~/.openclaw/media/inbound/, the same shared media store other channel plugins use. - LINE webhooks include ids but no names, so the sender's display name and the group's name are looked up once and cached for five minutes. Group and room members are read through their conversation, since that is the only route to a member who has not friended the bot. Should either lookup fail, the raw id is used and the message still goes through. Multi-person rooms expose no name API, so their room id is kept.
- LINE attaches metadata and alternative text to inline emoji. When
()alternatives are empty, the agent sees[emoji]. Alternatives that carry meaning, such as(hello)and parentheses the sender typed, are kept as-is. - Several images chosen in a single action arrive from LINE as one webhook event per image, out of order. They are briefly held and then answered as one turn containing every image, sorted by the index LINE reports; if the sender's client omits that index, which happens on LINE 11.15 and earlier for Android, the delivery order is kept instead. Rather than holding a set forever, an incomplete set is delivered with whatever arrived, a few seconds after its newest part, or after the chat's queue reaches it when it is still waiting its turn. Anything else that shows up in the meantime, whether a message or another batch of images, waits behind it, so replies follow the order the chat was sent in; within a group that queue spans the whole room, since LINE orders conversations per chat and not per member. Unless
tools.media.image.attachmentssets bothmode: "all"andmaxAttachments, a model without native vision reads just the first image; setting either key alone keeps the limit at one. A set whose parts never announce a total is delivered with whatever arrived and stays silent about the rest, since nothing declares how many there were.
Reply quoting
channels.line.replyToMode governs native quote replies, where an outbound reply visibly quotes the message it answers, which is how a group can tell who the bot is addressing:
| Value | Behavior |
|---|---|
"off" (default) | Do not quote automatically |
"first" | Quote only the first reply of a turn |
"all" | Quote every reply of a turn |
"batched", accepted by other channels, is turned down here: it separates a reply to a coalesced turn from a reply to an immediate one, and nothing along the LINE path flags a turn as coalesced, so that distinction never comes up.
Per-account override: channels.line.accounts.<id>.replyToMode. No per-chat-type override exists: replyToModeByChatType, which Slack, Signal, and Mattermost accept, is turned down here, so a single account quotes identically in direct chats and groups.
{ channels: { line: { replyToMode: "all" } } }
Just as on Telegram, "off" disables automatic quoting only: an explicit reply tag written by the agent is still respected. Quotes appear inline and remain visible in the conversation, so threading them hides nothing.
Rather than by message id, LINE quotes by a token issued with each inbound message, and OpenClaw can only quote a message whose token it retained. That puts limits on quoting which no setting can remove:
- A quote token is issued by LINE only for text, image, video, and sticker messages. Any reply answering another kind goes out unquoted.
- LINE refuses a quote on a Flex card, on media, and on a location pin, so a reply quotes once, on the earliest message able to carry it. A reply consisting only of those is sent unquoted.
- Only a message OpenClaw received can be quoted by a reply. LINE also returns a quote token for every message the bot sends, but those are not retained, so a reply answering one of the bot's own earlier messages goes out unquoted.
- Only a message OpenClaw passed to the agent as its own turn is remembered. In a group with
requireMentionon, a skipped message still reaches the agent as a line of group history, yet that line carries no id the reply can name, so quoting it is impossible. - A turn a person began by tapping a button has no message of its own, so its reply goes out unquoted.
- Tokens live in the running Gateway, the most recent 500 per account across all its chats. A reply answering a message from before the last restart, one a busier chat on the same account has since pushed out, or one sent by a separate process such as
openclaw message send, goes out unquoted. - When LINE rejects a request carrying a quote token with HTTP 400, OpenClaw retries the same reply minus the quote. Deleting or unsending the quoted message does not by itself invalidate its token; LINE may instead display the quoted content as unavailable. See LINE quote messages.
Block streaming
With block streaming, each completed assistant block goes out as its own LINE message rather than waiting for the full reply. It is off by default, and channels.line.streaming controls it for LINE alone:
{ channels: { line: { streaming: { block: { enabled: true } } } } }
| Setting | Effect |
|---|---|
streaming.block.enabled: true | Send completed blocks as they finish, whatever the agent default is |
streaming.block.enabled: false | Keep LINE on whole replies even when the agent default is on |
| unset (default) | Follow agents.defaults.blockStreamingDefault |
streaming.block.coalesce | Merge small blocks before sending (minChars, maxChars, idleMs) |
streaming.chunkMode | length (default) or newline to split on paragraph boundaries |
Per-account override: channels.line.accounts.<id>.streaming. These are the block-mode streaming controls shared across channels; see Streaming.
Every block LINE receives counts as a separate message, and LINE charges messages against the channel's monthly quota, so keeping this off holds a long reply to the fewest messages. coalesce.minChars is the lever when you want blocks to arrive early but not one paragraph at a time; OpenClaw's own default is 800 characters.
Since LINE cannot edit a message already sent, it offers no preview streaming mode: there is no streaming.mode or streaming.preview here, and a reply is never revised in place.
Structured rich messages
For portable choices, use the shared message presentation fields. LINE renders buttons blocks as Flex controls and select blocks as quick replies. The portable confirm-style form is a two-button block.
A buttons block renders a Flex card carrying the presentation's title and text. A presentation whose only control is a select renders no card, because quick replies attach to the reply's own text message. Instead, its title and text blocks are appended to that text. Across every select block in the reply, not per block, LINE draws at most 13 quick replies on one message. In that text, each select keeps its prompt and any overflow options together. Prompts and overflow option names stay complete. Only native quick-reply button labels are shortened to LINE's 20-character limit.
When a question using ask_user appears in a one-to-one chat, its choices are rendered as tappable controls right on the Flex card, and tapping one submits the answer. LINE transmits the index the Gateway assigned to that option, not its label, so if a reply arrives carrying a choice the Gateway no longer recognises, OpenClaw shows it as plain readable text rather than rendering a tap that would select the wrong answer. Only a single-select, non-secret question with two to four distinct options qualifies, matching the same limit applied by Telegram, Discord and Slack. Everything else remains readable text that can still be answered by typing a reply. The same readable fallback covers groups, multi-person chats, and destinations that are not recognised. Because LINE's group and room postbacks omit the sender identity required to accept a question answer, respond with the option text instead.
The Other… free-text control is never rendered. Tapping it accomplishes nothing on its own, and since LINE cannot withdraw a control from a card it has already sent, the button would only introduce a tap that changes nothing beyond what the question's text already provides. Discord and Slack keep that route in text for the same reason. In qualifying direct chats, every declared option retains a native control, while Other… remains named in the card's text under Actions: regardless of how many options exist.
Since LINE cannot edit a message once delivered, the controls remain visible after the question has ended. A tap arriving at that point is answered with That question is no longer waiting for an answer.. Initial taps go through the channel's usual admission and pairing rules. Should pairing be revoked while the question is being read, the answer is dropped with no answer notice and no fresh pairing challenge. Answered, cancelled and expired questions all produce a single terminal state from the Gateway, so the notice never states which one occurred.
{
action: "send",
message: "Choose an action",
presentation: {
title: "Menu",
blocks: [
{
type: "buttons",
buttons: [
{ label: "Status", action: { type: "command", command: "/status" } },
{ label: "Website", action: { type: "url", url: "https://example.com" } },
],
},
{
type: "select",
placeholder: "Pick one",
options: [
{ label: "Alpha", action: { type: "callback", value: "alpha" } },
{ label: "Help", action: { type: "command", command: "/help" } },
],
},
],
},
}
LINE-only output relies on the schema-validated channelData.line fields on message(action="send"). Send one location and/or one card. Supported card types are media_player, event, agenda, device, and appletv_remote.
{
action: "send",
message: "Here you go",
channelData: {
line: {
location: {
title: "Office",
address: "123 Main St",
latitude: 35.681236,
longitude: 139.767125,
},
card: {
type: "event",
title: "Team meeting",
date: "2026-08-18",
time: "10:00",
location: "Conference room",
description: "Weekly planning",
},
},
},
}
Other card shapes:
{ type: "media_player", title: "Song", artist: "Artist", source: "Living Room", status: "playing", imageUrl: "https://example.com/cover.jpg" }
{ type: "agenda", title: "Today", events: [{ title: "Standup", time: "09:00", location: "Online" }] }
{ type: "device", name: "TV", deviceType: "Streaming box", status: "Playing", controls: [{ label: "Pause", action: "pause" }] }
{ type: "appletv_remote", name: "Living Room", status: "Playing" }
Strings wrapped in double brackets, like [[buttons: ...]], count as plain text and are never treated as rich-message instructions.
The LINE plugin also provides a /card command for Flex message presets:
/card info "Welcome" "Thanks for joining!"
Card images and icons have to be served over HTTPS. OpenClaw strips images whose URLs are malformed or not HTTPS, then adds an "Image unavailable" note provided it fits inside LINE's 30 KB bubble and 50 KB carousel limits. Video heroes retain their mandatory alternative content: if the video or preview URL cannot be used, that content takes over, and if the alternative image cannot be used, a text box replaces it. Template thumbnails that are invalid get removed. Carousel thumbnails are dropped as a set so that all columns keep an identical image layout. Text and action buttons are left untouched.
ACP support
ACP (Agent Communication Protocol) conversation bindings are supported on LINE:
/acp spawn <agent> --bind hereattaches the current LINE chat to an ACP session without spawning a child thread.- Configured ACP bindings and active conversation-bound ACP sessions behave on LINE the same way they do on other conversation channels.
See ACP agents for details.
Outbound media
Through the agent message tool, the LINE plugin delivers images, videos, and audio:
- Images: delivered as LINE image messages. The preview image defaults to the media URL.
- Videos: a preview image is required. Point
channelData.line.previewImageUrlat an image URL. - Audio: delivered as LINE audio messages. Unless
channelData.line.durationMsis set, the duration defaults to 60 seconds.
If mediaKind is left out, LINE derives it from LINE-specific options or from the URL suffix. Native suffix inference covers JPEG/PNG, MP4, and MP3/M4A. URLs without a suffix keep the image fallback. URLs with other suffixes, and inferred MP4 lacking a preview, turn into text links. An explicit video still needs previewImageUrl.
Outbound media URLs must be public HTTPS URLs no longer than 2000 characters. Before passing the URL to LINE, OpenClaw checks the target hostname and turns down loopback, link-local, and private-network targets.
Troubleshooting
- Webhook verification fails: verify the webhook URL uses HTTPS and that
channelSecretagrees with the LINE console. - No inbound events: run
openclaw channels status --probe. LINE delivers events only while the channel's webhook URL is registered and Use webhook is enabled in the Messaging API tab of the LINE Developers Console, and the probe reports both facts, a channel with the webhook off or unregistered is named along with the setting that needs changing. OpenClaw configures neither on your behalf: the URL has an API but relies on a public address OpenClaw has no knowledge of, and the Use webhook switch offers no API whatsoever. The webhook state is supplied by the probe, soopenclaw channels statuswithout--probewill not report it. Should the probe report the webhook as on, verify the webhook path matcheschannels.line.webhookPathand that LINE can reach the Gateway. - Media download errors: raise
channels.line.mediaMaxMbwhen media goes over the default limit. - Pushes refused with HTTP 429: run
openclaw channels status --channel line --probe --json. Under a limited allowance, the account'squotaholdsusedandlimit. A missing quota means unknown, not unlimited. A healthy bot identity can exist alongside an exhausted push allowance. Verify the account allowance or plan in LINE Official Account Manager before trying again. A 429 may also indicate rate limits or temporary message reservations. Unlike pushes, ordinary reply-token messages do not draw on this monthly allowance. See LINE message pricing. - Bot silently skips messages (events dead-lettered):
openclaw logslistsline: spooled update <id> ... dead-letteredlines together with the failure reason. Examine them withopenclaw channels dead-letters list --channel line --account defaultand review the failure reason before recovering:resubmitre-enqueues by event id without checking why the event failed. Once the cause of a failure with no committed side effects has been fixed (sayretry-limit-exceededfollowing a provider outage), re-enqueue a single event usingopenclaw channels dead-letters resubmit <event-id> --channel line --account default. Never resubmit adelivery-side-effects-committedevent: that reason indicates the delivery already adopted an agent turn or used up its reply token, so re-enqueuing repeats committed work, such as a second visible reply.openclaw healthreports dead-letter counts andopenclaw doctoridentifies the affected accounts. handler-timeoutretries: the delivery was claimed but for 5 minutes neither reached agent-turn adoption nor reported deferred progress. This is a stall before the turn begins, adoption clears the watchdog, so a turn already running is never the cause and is never interrupted by it. Instead, inspect the dispatch path: the delivery preparation between claim and adoption, such as inbound media download or a Gateway that is not accepting new work. The event is not dead-lettered by this.openclaw logsshowsapplying retry policy (handler-timeout)and the event rides out its backoff withhandler-timeoutas its last error. A stall that keeps recurring is what ultimately uses up the retry limit, so an event that stalls its way into a dead letter ends up underretry-limit-exceeded, not under a timeout reason. Checkopenclaw logs --followaround the affected event id.
Related
- Channels Overview, all supported channels
- Pairing, DM authentication and pairing flow
- Groups, group chat behavior and mention gating
- Channel routing, session routing for messages
- Security, access model and hardening