DEV Community

Cover image for Build a LINE and Discord Order Lookup Bot with n8n, LangBot and GPT-6 Astra
Rock
Rock

Posted on Fully Autonomous

Build a LINE and Discord Order Lookup Bot with n8n, LangBot and GPT-6 Astra

“Where is my order?” is a data lookup wearing the clothes of a chat question. A model cannot know whether a parcel shipped just because it can sound like a support agent.

Here the order ID extraction and lookup are deterministic. GPT-6 Astra only explains the record that the API actually returned. n8n connects the steps, and LangBot provides a LINE or Discord entry point. Missing IDs and unknown orders take a separate branch before any model call.

The workflow we will build

The workflow we will build

1. Start n8n and LangBot from scratch

You need Docker Compose and an OpenAI-compatible API credential with access to gpt-6-astra. The tested versions are n8n 2.39.6 and LangBot 4.10.11, with English interfaces. The model ID is the one provided by the gateway used in this lab; verify your own provider's ID and endpoint.

Create a new directory and save this as compose.yaml.

name: langbot-n8n-astra
services:
  n8n:
    image: docker.n8n.io/n8nio/n8n:2.39.6
    environment:
      TZ: Asia/Shanghai
      GENERIC_TIMEZONE: Asia/Shanghai
      N8N_HOST: localhost
      N8N_PORT: 5678
      N8N_PROTOCOL: http
      N8N_EDITOR_BASE_URL: http://localhost:5690
      WEBHOOK_URL: http://localhost:5690/
      N8N_DIAGNOSTICS_ENABLED: 'false'
      N8N_PERSONALIZATION_ENABLED: 'false'
    ports:
      - '127.0.0.1:5690:5678'
    volumes:
      - n8n_data:/home/node/.n8n
      - ./workflows:/imports:ro
    restart: unless-stopped
  demo-api:
    image: python:3.12-alpine
    command: [python, /app/demo-api.py]
    volumes:
      - ./demo-api.py:/app/demo-api.py:ro
      - ./data/demo:/data
    restart: unless-stopped
  runtime:
    image: rockchin/langbot:v4.10.11
    command: [uv, run, --no-sync, -m, langbot_plugin.cli.__init__, rt]
    volumes:
      - ./data/langbot/plugins:/app/data/plugins
    restart: unless-stopped
  langbot:
    image: rockchin/langbot:v4.10.11
    depends_on: [runtime, n8n]
    environment:
      TZ: Asia/Shanghai
      BOX__ENABLED: 'false'
      PLUGIN__RUNTIME_WS_URL: ws://runtime:5400/control/ws
      API__WEBUI_URL: http://localhost:5370
      API__WEBHOOK_PREFIX: http://localhost:5370
    volumes:
      - ./data/langbot:/app/data
    ports:
      - '127.0.0.1:5370:5300'
    restart: unless-stopped
volumes:
  n8n_data:
Enter fullscreen mode Exit fullscreen mode

Save demo-api.py beside it. This complete service contains synthetic orders and a persistent draft-task endpoint.

"""Local synthetic order/task service. No real customer data or external writes."""
from http.server import BaseHTTPRequestHandler, HTTPServer
import json
import sqlite3
from pathlib import Path

Path('/data').mkdir(exist_ok=True)
db = sqlite3.connect('/data/tasks.sqlite')
db.execute('CREATE TABLE IF NOT EXISTS meetings (id TEXT PRIMARY KEY, payload TEXT NOT NULL)')
ORDERS = {
 'DEMO-1001': {'order_id':'DEMO-1001','status':'shipped','carrier':'Demo Express','tracking':'DEMO-TRACK-901','estimated_delivery':'2026-09-20','items':['USB-C Hub']},
 'DEMO-1002': {'order_id':'DEMO-1002','status':'processing','carrier':None,'tracking':None,'estimated_delivery':None,'items':['Desk Lamp']},
}
class Handler(BaseHTTPRequestHandler):
 def reply(self, data, code=200):
  body=json.dumps(data,ensure_ascii=False).encode();self.send_response(code);self.send_header('Content-Type','application/json');self.end_headers();self.wfile.write(body)
 def do_GET(self):
  if self.path.startswith('/orders/'):
   key=self.path.rsplit('/',1)[-1];self.reply({'found':key in ORDERS,'order':ORDERS.get(key),'source':'Synthetic tutorial order database'})
  elif self.path=='/tasks':
   self.reply({'meetings':[json.loads(x[0]) for x in db.execute('SELECT payload FROM meetings ORDER BY rowid DESC LIMIT 20')]})
  else:self.reply({'service':'LangBot tutorial demo API','fixtures':True})
 def do_POST(self):
  if self.path!='/tasks':return self.reply({'error':'Not found'},404)
  try:
   obj=json.loads(self.rfile.read(int(self.headers.get('Content-Length','0'))));key=obj['meeting_id']
   if not isinstance(obj.get('tasks'),list) or not key:raise ValueError('Invalid task payload')
   db.execute('INSERT INTO meetings VALUES (?,?) ON CONFLICT(id) DO UPDATE SET payload=excluded.payload',(key,json.dumps(obj,ensure_ascii=False)));db.commit()
   self.reply({'saved':True,'meeting_id':key,'task_count':len(obj['tasks']),'record':obj})
  except (ValueError,KeyError):self.reply({'error':'Invalid task payload'},400)
HTTPServer(('0.0.0.0',8080),Handler).serve_forever()
Enter fullscreen mode Exit fullscreen mode
mkdir -p workflows
docker compose up -d
docker compose ps
Enter fullscreen mode Exit fullscreen mode

Open http://localhost:5690 to create the local n8n owner, then initialize LangBot at http://localhost:5370. Skip bot onboarding for now and create the pipeline first. These ports bind to loopback for the lab. Docker volumes and the data directory retain state across container restarts.

The local n8n owner setup screen

The local n8n owner setup screen
In LangBot 4.10.11, the native n8n runner lives in the pipeline settings. Later releases may expose it through a Runner plugin, so use the version shown here when following the screenshots.

2. Build the workflow with explicit steps

The fixture contains only two synthetic orders: DEMO-1001 is shipped, and DEMO-1002 is processing. No real merchant account or customer database is connected.

Extract Order ID recognizes DEMO- followed by four digits. If it finds none, it sends MISSING to the same lookup endpoint. The API responds with found and order; the IF node checks whether found is true.

The true branch sends the actual record and the original question to the model. The false branch returns a fixed message. Asking for DEMO-9999 therefore cannot trigger an invented tracking number. A processing order has no estimated delivery date, and the model should say so.

Before using real order data, authorize the chat user against the order owner. Knowing an order number is not sufficient authorization. That identity layer is outside this synthetic lab.

The complete workflow, from chat input to HTTP response

The complete workflow, from chat input to HTTP response
Create a workflow in n8n and add the nodes shown. Keep the names below unchanged: expressions refer to upstream nodes by name. Configure them as follows.

Node Type Settings
Chat Webhook webhook POST; Path: astra-order; Respond: Using Respond to Webhook Node
Extract Order ID code Run Once for All Items; JavaScript
Look Up Demo Order httpRequest GET; http://demo-api:8080/orders/{{$json.order_id}} (Expression)
Order Found? if {{ $json.found }}is true
GPT-6 Astra - Explain Status httpRequest POST; https://newapi.rockchin.top/v1/chat/completions; Header Auth
Missing Order Reply code Run Once for All Items; JavaScript
Reply to LangBot code Run Once for All Items; JavaScript
Respond to Chat respondToWebhook Text; Content-Type: application/json; charset=utf-8

Calling the order API with the extracted ID

Calling the order API with the extracted ID
The IF node branches on the found field

The IF node branches on the found field

Model credential and request body

Create a Header Auth credential in n8n: Name is Authorization, Value is Bearer YOUR_API_KEY. In the model HTTP Request node, select Generic Credential Type → Header Auth and bind that credential. Do not put a real key in Code nodes or workflow exports. LangBot does not automatically pass its model credential to n8n.

Enable Send Body → JSON → Using JSON, switch to Expression, and enter the expression below. The URL shown is the compatible gateway used for this lab; change it and the model ID for your own provider.

{{
JSON.stringify({
  model: "gpt-6-astra",
  messages: [
    {
      role: "system",
      content:
        "Explain the supplied synthetic order status in the language of the customer question. Use only the order data. Do not invent arrival guarantees, carrier links, contact details or refunds. Mention that this is demo data. Keep under 120 words. An estimated date is not a guarantee.",
    },
    {
      role: "user",
      content: JSON.stringify({
        question: $("Extract Order ID").first().json.question,
        data: $json,
      }),
    },
  ],
  stream: false,
  max_tokens: 1500,
})
}}
Enter fullscreen mode Exit fullscreen mode

Code node contents

Extract Order ID

const text = String($json.body.chatInput || "");
const id = (text.match(/DEMO-\d{4}/i) || [])[0];
return [
  { json: { question: text, order_id: id ? id.toUpperCase() : "MISSING" } },
];
Enter fullscreen mode Exit fullscreen mode

Missing Order Reply

const id = $("Extract Order ID").first().json.order_id;
return [
  {
    json: {
      response:
        id === "MISSING"
          ? "Please include a demo order ID, such as DEMO-1001. 请提供演示订单号,例如 DEMO-1001。"
          : "No matching demo order was found. Please check the ID. 没有找到该演示订单,请核对编号。",
    },
  },
];
Enter fullscreen mode Exit fullscreen mode

Reply to LangBot

return [{ json: { response: $json.choices[0].message.content } }];
Enter fullscreen mode Exit fullscreen mode

Connections and the final response

  • Chat WebhookExtract Order ID
  • Extract Order IDLook Up Demo Order
  • Look Up Demo OrderOrder Found?
  • Order Found?GPT-6 Astra - Explain Status (true)
  • Order Found?Missing Order Reply (false)
  • GPT-6 Astra - Explain StatusReply to LangBot
  • Missing Order ReplyRespond to Chat
  • Reply to LangBotRespond to Chat

The final Respond to Chat is a Respond to Webhook node. Choose Text, add the response header Content-Type: application/json; charset=utf-8 in Options, and use this Response Body expression:

{{
JSON.stringify($json).replace(
  /[\u007f-\uffff]/g,
  (c) => "\\u" + c.charCodeAt(0).toString(16).padStart(4, "0"),
)
}}
Enter fullscreen mode Exit fullscreen mode

This is still a JSON response. Non-ASCII characters are escaped on the wire and restored by JSON parsing. It avoids a chunk-decoding issue observed with long Chinese replies in this LangBot build. The final object must contain a string named response. Set Options → Timeout in the model HTTP Request to 90000 milliseconds.

Click Publish when the workflow is ready. In n8n 2.39.6, that enables its production webhook. Do not leave LangBot pointing at a temporary /webhook-test/ URL after a manual test.

An explicit JSON response that preserves Unicode text

An explicit JSON response that preserves Unicode text

3. Connect the workflow to LangBot

Create a pipeline in LangBot. Open Configuration → AI and select n8n Workflow API as the Runner. Use:

  • Webhook URL: http://n8n:5678/webhook/astra-order
  • Output Key: response
  • Timeout: 150
  • Webhook Response Handling: Forward as chat reply
  • Authentication Type: None (this isolated local lab only)

The hostname is the common gotcha. Your browser uses localhost:5690; the LangBot container reaches the n8n service at n8n:5678 on the Compose network. Inside that container, localhost points back to LangBot itself.

For a deployment across hosts, use HTTPS and configure matching Header Auth or Basic Auth on the n8n Webhook and LangBot runner. The unauthenticated tutorial endpoints are limited to the local lab, not a public deployment recipe.

The n8n Runner settings in LangBot; use this case's webhook path

The n8n Runner settings in LangBot; use this case's webhook path
Save the pipeline, open Debug Chat, and send:

Please check order DEMO-1001. Has it shipped, and what is the estimated delivery date?
Enter fullscreen mode Exit fullscreen mode

A real request and reply through LangBot Debug Chat

A real request and reply through LangBot Debug Chat
The shipped-order test returned the fixture carrier, DEMO-TRACK-901, and the estimated date, with no delivery guarantee. The unknown-ID test returned a fixed not-found message rather than fabricated logistics.

A message without an ID asks the user to supply one. Neither of those two branches needs a GPT call. The API remains the source of truth; the model's job is to make its fields readable.

The n8n execution view lets you inspect each step

The n8n execution view lets you inspect each step

4. Put it behind your messaging platform

The tested path is LangBot Debug Chat → n8n → model/data API → LangBot reply. No real Slack, Discord, Telegram or LINE account was connected for these screenshots. The following is the adapter setup path, not a claim of live delivery to those platforms.

Platform Platform-side setup LangBot setup
Slack Create a Slack app, configure bot scopes and subscribed events, install it in the workspace; retain the bot token and signing secret Select Slack, enter those credentials, and register the callback URL shown by LangBot in Event Subscriptions. Reinstall after changing scopes. Guide
Discord Create an application and bot; obtain the client ID and bot token, enable the required gateway intents, and invite the bot with the required permissions Select Discord and enter the client ID and token. Bind the pipeline below. Guide
Telegram Create a bot with BotFather and retain its token. For group use, configure privacy mode for the messages the bot should receive Select Telegram, enter the token, save, and bind the pipeline. Guide
LINE Enable Messaging API for the channel, obtain the channel secret and channel access token Select LINE, enter both values, copy LangBot's public HTTPS callback URL to LINE, and enable Use Webhook. Guide
Lark / Feishu Create an app with bot capability and message permissions Enter App ID / App Secret in Lark. Use long connection where supported, or configure the generated webhook callback. Guide
DingTalk Create an enterprise bot and enable Stream Mode Push Enter Client ID / Client Secret in DingTalk. Card streaming also needs a configured template and permissions. Guide
WeCom / WeChat Official Account Use the corresponding enterprise or official-account credentials and message-encryption settings Select the matching adapter and register its callback; the credential sets are different. WeCom, Official Account
QQ / OneBot Configure a QQ official bot or a running OneBot v11 implementation Choose QQ Official API or OneBot v11 and configure the callback/WebSocket connection. QQ, OneBot

LangBot also offers adapters for Mattermost, Matrix, KOOK and Satori, plus HTTP and embedded webpage bots. Personal WeChat options depend on the adapter and version. Check Create Bot and the platform index for your installed release.

The platform selector in the English LangBot interface

The platform selector in the English LangBot interface
After creating the bot, bind it to this pipeline and enable it. For group chats, also check the @-mention or prefix rules under Trigger. A bot that is online but silent may never be triggering the workflow. Callback-based adapters need reachable HTTPS; long-connection and polling adapters have their own network requirements.

5. Three places to check when it fails

  • Webhook 404: confirm the workflow is published and the URL uses /webhook/.
  • A successful execution but an empty chat reply: inspect the final JSON for a string named response, then check the runner's Output Key.
  • A model-node 401, timeout or empty result: check the credential bound inside n8n, the complete endpoint, the model ID and that execution's data. LangBot and n8n have separate model configurations.

I would lock down the input, evidence and response contract before adding more models or channels. LangBot makes the division useful: business steps stay in n8n, while a new chat destination mainly changes the adapter and pipeline binding.

References: LangBot n8n integration, LangBot source, n8n source and releases.

Configuration and screenshots come from a running local lab. Test inputs are synthetic. AI assisted with drafting; the instructions were checked against execution records.

Top comments (0)