LINE Channel Setup and Configuration for OpenClaw

This page explains how to install and configure the LINE Messaging API plugin for OpenClaw, covering webhook setup, authentication, and supported message types. It is intended for developers integrating LINE with OpenClaw.

Read this when

  • You want to connect OpenClaw to LINE
  • You need LINE webhook + credential setup
  • You want LINE-specific message options

LINE integrates with OpenClaw through the LINE Messaging API. Acting as a webhook receiver on the Gateway, the plugin authenticates using your channel access token together with the channel secret.

This is an official plugin that requires separate installation. It handles direct messages, group chats, media, locations, Flex messages, template messages, and quick replies. Neither reactions nor threads are supported.

Install

Before configuring the channel, install LINE:

openclaw plugins install @openclaw/line

For a local checkout (when operating from a git repository):

openclaw plugins install ./path/to/local/line-plugin

Setup

  1. Sign up for a LINE Developers account, then access the Console at https://developers.line.biz/console/.
  2. Choose or create a Provider, then add a Messaging API channel.
  3. From the channel settings, copy both the Channel access token and Channel secret.
  4. Within Messaging API settings, turn on Use webhook.
  5. Point the webhook URL at your gateway endpoint, which must use HTTPS:
https://gateway-host/line/webhook

The Gateway responds to LINE's signed webhook verification request by returning a POST that contains an empty events list. For signed inbound events, each one gets written to the durable ingress queue before a 200 response is sent; agent processing happens asynchronously after that. Undelivered events are retried from the queue, even across a Gateway restart, and poison events become failed queue records once bounded retries are exhausted. When durable persistence cannot be completed, the request returns 500 rather than acknowledging an event that might be lost.

Delivery across the queue-to-agent boundary is at least once: a Gateway shutdown or crash during an active delivery can cause the turn to be replayed. Message events are deduplicated by LINE message ID; other event types rely on webhookEventId. Retained completion records keep ordinary duplicate webhooks from being processed, but handlers with external side effects should still be written idempotently.

For a custom path, configure channels.line.webhookPath or channels.line.accounts.<id>.webhookPath and adjust the URL to match.

Security considerations:

  • LINE's signature verification depends on the body (HMAC computed over the raw body), so OpenClaw enforces a strict 64 KB pre-auth body limit and a read timeout before verification runs.
  • OpenClaw handles webhook events from the verified raw request bytes. Any upstream middleware-transformed req.body values are disregarded to preserve signature integrity.

Inbound durability

The Setup webhook contract only acknowledges an event once it has been durably queued. The durable 200 carries x-openclaw-delivery-accepted: durable; signed verification pings (which have empty event lists) and error responses leave out the marker, letting reverse proxies require it to tell durable acceptance apart from a generic 200. From that point, delivery proceeds through the standard channel-ingress drain with LINE-specific behavior:

  • Per-conversation ordering. Events are serialized by source lane, either group:<groupId>, room:<roomId>, or user:<userId>; events lacking a conversation source get their own event-scoped lane. Within a single lane, events dispatch in the order they arrived, so a retrying event holds up later events in that same chat. One chat's backlog cannot block another chat's lane, yet all lanes share a limit of 8 concurrent deliveries: other chats move forward independently whenever a slot is free, and a 9th lane waits until one opens up.
  • Retries. A failed delivery is retried with exponential backoff that begins at 1 second and doubles with each attempt, totaling roughly two minutes of cumulative backoff across the window. Once the 8th attempt fails, the event dead-letters (retry-limit-exceeded) right away: LINE opts out of the generic 24-hour dead-letter age floor so a poison event cannot tie up its conversation lane for a full day.
  • Non-retryable failures. These dead-letter immediately, with no retries regardless of how many attempts were made: stored payloads that no longer parse (invalid-event), deliveries that already committed side effects (delivery-side-effects-committed), and LINE API authentication failures (authentication-failed, HTTP 401/403).
  • Stall watchdog. A claimed delivery that neither reaches agent-turn adoption nor reports continued deferred progress for 5 minutes is aborted and returned to its lane under the same retry policy as any other failure: the event goes back to pending with its attempt count incremented and handler-timeout recorded as its last error, keeping its position at the head of its lane. A stall is not itself a dead letter; only the retry limit above terminates the event, as retry-limit-exceeded. The watchdog covers only the window between claim and adoption: deferred progress re-arms it and adoption clears it, so a long agent turn is never interrupted. Adoption arriving after the watchdog fires is fenced off, meaning a late turn cannot claim an event that has already been handed back.
  • Crash recovery. Each drain pass starts with a recovery sweep that reclaims any claim whose owning Gateway process is no longer running, so a delivery lost to a hard crash gets retried on the next sweep rather than waiting for a timeout. The 30-minute claim lease is the fallback bound for the opposite case: without a successful lease refresh, it limits how long a claim stays protected solely 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. On every admission, LINE removes completed and failed queue records older than 30 days, then keeps the 4096 most recently updated records of each kind per account. Since pruning runs before the new event is queued rather than on a timer, records can persist past 30 days on an idle account, and a newly completed record can push the count above 4096 until the next admission. While a record exists, a redelivered webhook for the same event is acknowledged without a second dispatch; once it disappears, whether by age or by cap, a redelivery is admitted and dispatched again, so handlers with external side effects should not treat this window as a replacement for their own idempotency.

The 500-on-persistence-failure contract only works if LINE re-sends the event. LINE redelivers a webhook when Webhook redelivery is enabled for the channel in the LINE Developers Console (Messaging API settings, alongside Use webhook) and the bot server did not answer 2xx. Without that setting, an event refused with 500 is not re-sent. Even when enabled, redelivery is best effort rather than guaranteed: LINE documents that it is not reliable, that the retry count and interval are undisclosed, and that 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 configuration:

{
  channels: {
    line: {
      enabled: true,
      channelAccessToken: "LINE_CHANNEL_ACCESS_TOKEN",
      channelSecret: "LINE_CHANNEL_SECRET",
      dmPolicy: "pairing",
    },
  },
}

Public DM configuration:

{
  channels: {
    line: {
      enabled: true,
      channelAccessToken: "LINE_CHANNEL_ACCESS_TOKEN",
      channelSecret: "LINE_CHANNEL_SECRET",
      dmPolicy: "open",
      allowFrom: ["*"],
    },
  },
}

Environment variables (default account only):

  • LINE_CHANNEL_ACCESS_TOKEN
  • LINE_CHANNEL_SECRET

Token and secret files:

{
  channels: {
    line: {
      tokenFile: "/path/to/line-token.txt",
      secretFile: "/path/to/line-secret.txt",
    },
  },
}

Both tokenFile and secretFile must reference regular files. Symlinks are rejected. Inline config values take precedence over files; environment variables serve as 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 (default pairing)
  • channels.line.allowFrom: allowlisted LINE user IDs for DMs; dmPolicy: "open" requires ["*"]
  • channels.line.groupPolicy: allowlist | open | disabled (default allowlist)
  • channels.line.groupAllowFrom: allowlisted LINE user IDs for groups; DM allowFrom entries do not admit group senders
  • Per-group overrides: channels.line.groups.<groupId>.allowFrom (plus enabled, requireMention, systemPrompt, skills). With groupPolicy: "allowlist", set groupAllowFrom or the per-group allowFrom; an empty group allowlist blocks group messages even when DMs are open.
  • Quoting one of the bot's own messages counts as addressing it, so a group reply made with LINE's quote gesture reaches the agent without an explicit mention. LINE does not read the implicitMentions flags, so this always counts; see Groups. The bot recognizes a quote of its own message from the most recent ones it remembers sending (a few hundred per account), so quoting an older message, or one sent before the last Gateway restart, still needs a mention.
  • Static sender access groups can be referenced from allowFrom, groupAllowFrom, and per-group allowFrom with accessGroup:<name>; see Access groups.
  • Runtime note: if channels.line is completely missing, runtime falls back to groupPolicy="allowlist" for group checks (even if channels.defaults.groupPolicy is 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

Group join introductions

When the bot joins an allowed group or multi-person room, it posts one introduction there. LINE exposes a group name through its group summary API, but no room name or topic for multi-person rooms. The Messaging API cannot read prior messages, so introductions use only available metadata and ask what the room wants the bot to take on rather than inventing activity.

Introductions are enabled by default. Set channels.line.joinIntro: false to disable them, or use channels.line.accounts.<accountId>.joinIntro to override one account. They never run in one-to-one user chats or when another member joins. See group join introductions for room admission, once-per-room behavior, and the no-tools turn that treats room content as untrusted.

Message behavior

  • Text is chunked at 5000 characters.
  • Markdown formatting is stripped; code blocks and tables are converted into Flex cards when possible.
  • Streaming responses are buffered; LINE receives full chunks with a loading animation while the agent works.
  • Media downloads are capped by channels.line.mediaMaxMb (default 10).
  • Inbound media is saved under ~/.openclaw/media/inbound/ before it is passed to the agent, matching the shared media store used by other channel plugins.
  • LINE webhooks carry ids but no names, so the sender's display name and the group's name are fetched once and cached for five minutes. Group and room members are read through their conversation, which is the only way to see a member who has not added the bot as a friend. If either lookup fails the raw id is used and the message is still delivered. Multi-person rooms have no name API, so they keep their room id.

Structured rich messages

Use the shared message presentation fields for portable choices. LINE renders buttons blocks as Flex controls and select blocks as quick replies. A two-button block is the portable confirm-style form.

{
  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 uses the schema-validated channelData.line fields on message(action="send"). Send one location and/or one card. The 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" }

Double-bracket strings such as [[buttons: ...]] are plain text and are not interpreted as rich-message instructions.

The LINE plugin also ships a /card command for Flex message presets:

/card info "Welcome" "Thanks for joining!"

ACP support

LINE supports ACP (Agent Communication Protocol) conversation bindings:

  • /acp spawn <agent> --bind here binds the current LINE chat to an ACP session without creating a child thread.
  • Configured ACP bindings and active conversation-bound ACP sessions work on LINE like other conversation channels.

See ACP agents for details.

Outbound media

The LINE plugin transmits images, videos, and audio via the agent message tool:

  • Images: delivered as LINE image messages; the preview image falls back to the media URL by default.
  • Videos: a preview image is mandatory; assign an image URL to channelData.line.previewImageUrl.
  • Audio: sent as LINE audio messages; the duration is 60 seconds unless channelData.line.durationMs overrides it.

If mediaKind is not provided, LINE derives it from LINE-specific options or the URL extension. Native extension detection covers JPEG/PNG, MP4, and MP3/M4A; URLs without an extension keep the image fallback. Other extensions and inferred MP4 lacking a preview are converted to text links. An explicit video still needs previewImageUrl.

Outbound media URLs must be public HTTPS endpoints capped at 2000 characters. OpenClaw checks the target hostname before passing the URL to LINE and blocks loopback, link-local, and private-network destinations.

Troubleshooting

  • Webhook verification fails: the webhook URL must be HTTPS and channelSecret has to match the LINE console.
  • No inbound events: verify the webhook path aligns with channels.line.webhookPath and that the gateway is accessible from LINE.
  • Media download errors: raise channels.line.mediaMaxMb when media exceeds the default limit.
  • Bot silently skips messages (events dead-lettered): openclaw logs displays line: spooled update <id> ... dead-lettered entries with the failure reason. Examine with openclaw channels dead-letters list --channel line --account default and review the failure reason before recovery: resubmit re-queues by event id without validating why the event failed. Once the cause of a failure with no committed side effects is fixed (for instance retry-limit-exceeded after a provider outage), re-queue a single event using openclaw channels dead-letters resubmit <event-id> --channel line --account default. Do not resubmit a delivery-side-effects-committed event: that reason indicates the delivery already claimed an agent turn or used up its reply token, so re-queuing duplicates the committed work, such as a second visible reply. openclaw health reports dead-letter totals and openclaw doctor lists affected accounts.
  • handler-timeout retries: the delivery was claimed but neither reached agent-turn adoption nor logged deferred progress within 5 minutes. This is a stall before the turn starts; adoption clears the watchdog, so an active turn is never the cause and is never interrupted by it. Inspect the dispatch path instead: the delivery preparation between claim and adoption, like inbound media download or a Gateway that refuses new work. This does not dead-letter the event; openclaw logs shows applying retry policy (handler-timeout) and the event waits through its backoff with handler-timeout as its final error. A recurring stall is what eventually depletes the retry limit, so an event that stalls into a dead letter falls under retry-limit-exceeded, not a timeout reason. Inspect openclaw logs --follow near the affected event id.
2,583 words · updated Sep 1, 2026