繁體中文 English

linekit

A modular, type-safe, secure toolkit for LINE services.
Modular
Install only what you need: Core, Messaging, Login, or Adapters.
Type-Safe
Built with TypeScript. Complete type definitions included.
Secure
Timing-safe signature verification, input validation, CSRF protection.
Framework Agnostic
Works with Express, Fastify, Next.js, or standard Fetch API.

Getting Started

Prerequisites

Installation

# For LINE Bot development
npm install @linekit/core @linekit/messaging @linekit/express

# For LINE Login
npm install @linekit/login

# Or install everything
npm install @linekit/core @linekit/messaging @linekit/login @linekit/express

Quick Start: Echo Bot

import express from "express";
import { createLineApp } from "@linekit/core";
import { lineMiddleware } from "@linekit/express";

const app = express();

const config = {
  channelSecret: process.env.CHANNEL_SECRET,
  channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN,
};

const line = createLineApp(config);

app.post("/webhook",
  lineMiddleware(config),
  line.router({
    message: async (ctx) => {
      if (ctx.event.message.type === "text") {
        await ctx.replyText(`You said: ${ctx.event.message.text}`);
      }
    },
  })
);

app.listen(3000, () => console.log("Bot running on port 3000"));

Packages Overview

@linekit/core

Webhook verification with timing-safe signatures, Context for easy replies, Router with error handling.

@linekit/messaging

Full Messaging API: reply, push, multicast, broadcast. Rich Menu management. Input validation included.

@linekit/login

OAuth 2.1 flow, ID Token verification, CSRF state helpers for secure authentication.

@linekit/express

Express.js adapter with automatic body parsing and signature verification middleware.

@linekit/liff

LIFF management API client (CRUD) and type definitions for building Line Front-end Framework apps.

LINE API Compatibility

linekit is built against the following LINE API versions:

API Version Reference
Messaging API v2 Documentation
LINE Login v2.1 (OAuth 2.1) Documentation
LIFF v2 Documentation

Endpoint Base URLs

API Limits (enforced by linekit)

Messages per Request

Maximum 5 message objects per reply/push/multicast request.

Multicast Recipients

Maximum 500 user IDs per multicast request.

Tutorials

1. Building a LINE Bot

This tutorial covers creating a fully-featured LINE Bot that responds to messages, follows, and postbacks.

How Webhook Works

User
sends message
→
LINE Platform
receives & forwards
→
Your Server
linekit processes
→
LINE API
sends reply
→
User
receives reply

Step 1: Set up your project

mkdir my-line-bot && cd my-line-bot
npm init -y
npm install express @linekit/core @linekit/messaging @linekit/express
npm install -D typescript @types/node @types/express tsx

Step 2: Create the bot

// src/index.ts
import express from "express";
import { createLineApp } from "@linekit/core";
import { lineMiddleware } from "@linekit/express";

const app = express();

const config = {
  channelSecret: process.env.CHANNEL_SECRET!,
  channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN!,
};

const line = createLineApp(config);

// Webhook endpoint
app.post("/webhook",
  lineMiddleware(config),
  line.router({
    // Handle text messages
    message: async (ctx) => {
      const { message } = ctx.event;

      if (message.type === "text") {
        // Echo the message back
        await ctx.replyText(`Echo: ${message.text}`);
      } else if (message.type === "sticker") {
        // Reply with the same sticker
        await ctx.reply({
          type: "sticker",
          packageId: message.packageId,
          stickerId: message.stickerId,
        });
      } else if (message.type === "image") {
        await ctx.replyText("Nice image!");
      }
    },

    // Handle new followers
    follow: async (ctx) => {
      await ctx.replyText("Thanks for following! Send me a message.");
    },

    // Handle postback events (from buttons, rich menus)
    postback: async (ctx) => {
      const data = ctx.event.postback.data;
      await ctx.replyText(`You clicked: ${data}`);
    },
  }, {
    // Optional: Custom error handler
    onError: (error, ctx) => {
      console.error(`Error for user ${ctx.userId}:`, error);
    }
  })
);

app.listen(3000, () => {
  console.log("Bot running on http://localhost:3000");
});

Step 3: Run your bot

# Set environment variables
export CHANNEL_SECRET="your-channel-secret"
export CHANNEL_ACCESS_TOKEN="your-channel-access-token"

# Run with tsx
npx tsx src/index.ts
Tip: Use ngrok or similar tools to expose your local server for webhook testing: ngrok http 3000

2. LINE Login Integration

Implement "Log in with LINE" in your web application with CSRF protection.

OAuth 2.1 Login Flow

User
clicks login
→
Your App
generates state
→
LINE Auth
user logs in
→
Callback
validates state
→
LINE API
exchange token
→
Logged In
user profile
// src/auth.ts
import {
  login,
  generateAuthUrl,
  issueAccessToken,
  LineLoginError
} from "@linekit/login";

const CHANNEL_ID = process.env.LINE_LOGIN_CHANNEL_ID!;
const CHANNEL_SECRET = process.env.LINE_LOGIN_CHANNEL_SECRET!;
const REDIRECT_URI = "https://example.com/callback";

// Step 1: Generate login URL
export function getLoginUrl(session: any) {
  // Generate secure random state for CSRF protection
  const state = login.generateState();
  const nonce = login.generateNonce();

  // Store in session for later validation
  session.lineLoginState = state;
  session.lineLoginNonce = nonce;

  return generateAuthUrl({
    channelId: CHANNEL_ID,
    redirectUri: REDIRECT_URI,
    state,
    nonce,
    scope: "profile openid email",
  });
}

// Step 2: Handle callback
export async function handleCallback(
  code: string,
  returnedState: string,
  session: any
) {
  // Validate state to prevent CSRF attacks
  if (!login.validateState(session.lineLoginState, returnedState)) {
    throw new Error("Invalid state - possible CSRF attack");
  }

  try {
    // Exchange code for tokens
    const tokens = await issueAccessToken(
      CHANNEL_ID,
      CHANNEL_SECRET,
      code,
      REDIRECT_URI
    );

    // Verify ID token and get user profile
    const user = await login.verify(tokens.id_token!, CHANNEL_ID);

    return {
      userId: user.sub,
      name: user.name,
      picture: user.picture,
      email: user.email,
      accessToken: tokens.access_token,
    };
  } catch (error) {
    if (error instanceof LineLoginError) {
      console.error(`Login failed: ${error.code}`);
    }
    throw error;
  }
}

// Express routes example
app.get("/login", (req, res) => {
  const url = getLoginUrl(req.session);
  res.redirect(url);
});

app.get("/callback", async (req, res) => {
  const { code, state } = req.query;
  const user = await handleCallback(code, state, req.session);
  req.session.user = user;
  res.redirect("/dashboard");
});

3. Sending Different Message Types

LINE supports various message types. Here's how to send each one.

import {
  replyMessage,
  pushMessage,
  multicast
} from "@linekit/messaging";

const TOKEN = process.env.CHANNEL_ACCESS_TOKEN!;

// Text message
await pushMessage(TOKEN, userId, [{
  type: "text",
  text: "Hello, World!"
}]);

// Text with emojis
await pushMessage(TOKEN, userId, [{
  type: "text",
  text: "Hello $ LINE emoji!",
  emojis: [{
    index: 6,
    productId: "5ac1bfd5040ab15980c9b435",
    emojiId: "001"
  }]
}]);

// Sticker
await pushMessage(TOKEN, userId, [{
  type: "sticker",
  packageId: "446",
  stickerId: "1988"
}]);

// Image
await pushMessage(TOKEN, userId, [{
  type: "image",
  originalContentUrl: "https://example.com/image.jpg",
  previewImageUrl: "https://example.com/preview.jpg"
}]);

// Location
await pushMessage(TOKEN, userId, [{
  type: "location",
  title: "LINE Office",
  address: "Tokyo, Japan",
  latitude: 35.687574,
  longitude: 139.72922
}]);

// Flex Message (for rich layouts)
await pushMessage(TOKEN, userId, [{
  type: "flex",
  altText: "This is a Flex Message",
  contents: {
    type: "bubble",
    body: {
      type: "box",
      layout: "vertical",
      contents: [
        { type: "text", text: "Hello", weight: "bold", size: "xl" },
        { type: "text", text: "World", size: "md" }
      ]
    }
  }
}]);

// Send to multiple users (up to 500)
await multicast(TOKEN, [userId1, userId2, userId3], [{
  type: "text",
  text: "Hello everyone!"
}]);

4. Rich Menu Management

Create and manage rich menus programmatically.

import { richMenu } from "@linekit/messaging";

const TOKEN = process.env.CHANNEL_ACCESS_TOKEN!;

// Create a rich menu
const menu = await richMenu.create(TOKEN, {
  size: { width: 2500, height: 1686 },
  selected: true,
  name: "Main Menu",
  chatBarText: "Menu",
  areas: [
    {
      bounds: { x: 0, y: 0, width: 1250, height: 843 },
      action: { type: "postback", data: "action=menu1" }
    },
    {
      bounds: { x: 1250, y: 0, width: 1250, height: 843 },
      action: { type: "uri", uri: "https://example.com" }
    },
    {
      bounds: { x: 0, y: 843, width: 1250, height: 843 },
      action: { type: "message", text: "Hello!" }
    },
    {
      bounds: { x: 1250, y: 843, width: 1250, height: 843 },
      action: { type: "postback", data: "action=menu4" }
    }
  ]
});

console.log("Rich menu created:", menu.richMenuId);

// Set as default for all users
await richMenu.setDefault(TOKEN, menu.richMenuId);

// Link to specific user
await richMenu.linkUser(TOKEN, userId, menu.richMenuId);

// Unlink from user
await richMenu.unlinkUser(TOKEN, userId);

// Delete rich menu
await richMenu.delete(TOKEN, menu.richMenuId);
Note: You'll need to upload a rich menu image separately using the LINE API. The image should be 2500x1686 or 2500x843 pixels.

5. Error Handling

Properly handle errors from LINE API calls.

import { LineClientError } from "@linekit/messaging";
import { LineLoginError } from "@linekit/login";

// Messaging API errors
try {
  await pushMessage(TOKEN, userId, messages);
} catch (error) {
  if (error instanceof LineClientError) {
    console.error(`LINE API Error: ${error.message}`);
    console.error(`Status: ${error.status}`);
    console.error(`Details:`, error.data);
  }
}

6. Marketing Push

Send messages to a large list of users with automatic chunking and rate limiting.

import { bulkMulticast } from "@linekit/messaging";

const userList = ["U1...", "U2...", /* 1000+ users */];

const results = await bulkMulticast(
  TOKEN,
  userList,
  [{ type: "text", text: "Weekly Update!" }],
  {
    chunkSize: 500, // LINE API limit
    delayMs: 1000   // Pause 1s between chunks
  }
);

const successCount = results.filter(r => r.success).reduce((acc, r) => acc + r.count, 0);
console.log(`Sent to ${successCount} users`);

7. LIFF App Management

Create and manage LIFF apps programmatically from your server.

import { LiffClient } from "@linekit/liff";

const client = new LiffClient({
  channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN
});

// Create a new LIFF app
const { liffId } = await client.add({
  view: {
    type: "full",
    url: "https://mysite.com/app"
  }
});

console.log("Created LIFF App: https://liff.line.me/" + liffId);
console.error(`Details:`, error.data); // Handle specific errors if (error.status === 400) { // Bad request - check message format } else if (error.status === 401) { // Invalid token } else if (error.status === 429) { // Rate limited - implement backoff } } } // Login errors try { const user = await login.verify(idToken, channelId); } catch (error) { if (error instanceof LineLoginError) { console.error(`Login Error: ${error.code}`); if (error.code === "INVALID_PARAMETER") { // Missing or invalid parameter } else if (error.code === "VERIFICATION_FAILED") { // Token verification failed } } } // Router error handling const router = line.router({ message: async (ctx) => { // Your handler code } }, { onError: async (error, ctx) => { console.error(`Error handling ${ctx.event.type}:`, error); // Log to monitoring service, send alert, etc. } });

API Reference

@linekit/core

@linekit/messaging

@linekit/login

Security Features

linekit is built with security as a priority:
Security Best Practices:
  • Never expose your channel secret or access token in client-side code
  • Always validate the OAuth state parameter
  • Use HTTPS for all webhook endpoints
  • Regularly rotate your access tokens