繁體中文 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