# How to Implement Message Handlers for Encrypted Messages in LINEJS

> Learn to implement message handlers for encrypted messages in LINEJS using the E2EE class. Encrypt payloads and decrypt messages seamlessly for secure communication. Discover LINEJS encryption.

- Repository: [Evex  Developers/linejs](https://github.com/evex-dev/linejs)
- Tags: how-to-guide
- Published: 2026-03-01

---

**Implementing message handlers for encrypted messages in LINEJS requires using the `E2EE` class to encrypt payloads with `encryptE2EEMessage` and decrypt them with `decryptE2EEMessage`, while `TalkService.sendMessage` automatically handles the encryption flow when the `e2ee` flag is enabled.**

The `evex-dev/linejs` repository provides native end-to-end encryption (E2EE) support for LINE Talk messages through a dedicated cryptographic module in the base package. This guide demonstrates how to implement secure message handlers that encrypt outgoing traffic and decrypt incoming payloads using the actual source implementation.

## Understanding E2EE Architecture in LINEJS

The LINEJS encryption flow follows a five-step lifecycle: detect the encryption requirement, encrypt the payload via `E2EE.encryptE2EEMessage`, transmit through `TalkService`, receive the encrypted response, and decrypt using `E2EE.decryptE2EEMessage`. All cryptographic operations reside in [`packages/linejs/base/e2ee/mod.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/e2ee/mod.ts), while the high-level messaging API in [`packages/linejs/base/service/talk/mod.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/service/talk/mod.ts) orchestrates the automatic encryption paths.

### Sending Encrypted Messages

The entry point for sending encrypted messages is `TalkService.sendMessage` in [`packages/linejs/base/service/talk/mod.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/service/talk/mod.ts) (lines 65‑78). When `e2ee` is set to `true` and no raw chunks are supplied, the method automatically delegates encryption to the E2EE module before transmission:

```typescript
if ((e2ee && !chunks && location) || (e2ee && !chunks && text)) {
    const chunks = await this.client.e2ee.encryptE2EEMessage(
        to,
        text || location || "invalid",
        contentType,
    );
    // …build contentMetadata with e2eeVersion/e2eeMark…
    return this.sendMessage({ …options, chunks });
}

```

The `E2EE.encryptE2EEMessage` method in [`packages/linejs/base/e2ee/mod.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/e2ee/mod.ts) (lines 438‑447) handles the cryptographic workflow:

```typescript
public async encryptE2EEMessage(
    to: string,
    data: string | Location | Record<string, LooseType>,
    contentType: LINETypes.ContentType = 0,
    specVersion = 2,
): Promise<Buffer[]> {
    // 1️⃣ Resolve sender/receiver keys
    // 2️⃣ Generate a shared secret (Diffie‑Hellman) → `keyData`
    // 3️⃣ Choose the proper branch (text, location, generic data)
    // 4️⃣ Build AAD, GCM key, sign, and encrypt (V2)
}

```

This method resolves the sender's private key from local storage, fetches the receiver's public key via `talk.negotiateE2EEPublicKey` for one-to-one chats or cached group key data for groups, generates a Curve25519 shared secret, and produces an array of five buffers: `salt`, `ciphertext+tag`, `sign`, `senderKeyId`, and `receiverKeyId`.

### Receiving and Decrypting Messages

When handling incoming messages, the `E2EE.decryptE2EEMessage` method in [`packages/linejs/base/e2ee/mod.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/e2ee/mod.ts) (lines 651‑674) serves as the primary handler:

```typescript
public async decryptE2EEMessage(messageObj: Message): Promise<Message> {
    if ((messageObj.contentType === "NONE" || …) && messageObj.chunks) {
        const [text, meta] = await this.decryptE2EETextMessage(messageObj);
        messageObj.text = text;
        messageObj.contentMetadata = { …messageObj.contentMetadata, …meta };
    } else if (messageObj.contentType === "LOCATION" && messageObj.chunks) {
        messageObj.location = await this.decryptE2EELocationMessage(messageObj);
    }
    return messageObj;
}

```

For text messages, `decryptE2EETextMessage` extracts the five buffers and calls `decryptE2EEMessageV2` (lines 928‑966), which re-derives the GCM key from the shared secret and salt, then performs AES-256-GCM decryption using the sign buffer as the IV.

### Automatic Fallback Mechanism

The `TalkService.sendMessage` implementation includes automatic E2EE detection. If the server returns an error containing "E2EE" and the original request did not set the encryption flag, the method catches the error, toggles `options.e2ee = true`, and retries automatically (lines 62‑70).

## Implementing the Message Handler Flow

### Sending Encrypted Text Messages

To implement a handler that sends encrypted messages, invoke `sendMessage` with the `e2ee` flag enabled:

```typescript
import { LineClient } from "@evex/linejs";

// Assume `client` is an authenticated LineClient
await client.talk.sendMessage({
  to: "U1234567890abcdef",   // recipient MID
  text: "Secret hello 👀",    // plain text
  e2ee: true,                // enable E2EE (optional – auto‑detect works)
});

```

This triggers the encryption branch in `TalkService`, generating encrypted chunks and setting metadata fields `e2eeVersion`, `e2eeMark`, and numeric key IDs automatically.

### Handling Incoming Encrypted Messages

Implement message handlers that process incoming encrypted data by checking for chunks and invoking the decryption method:

```typescript
// Pull latest events (including messages)
const events = await client.talk.sync({ limit: 20 });

// Find a regular message event
for (const ev of events) {
  if (ev.type === "RECEIVE_MESSAGE") {
    const msg = ev.message as Message;
    // Decrypt if needed (the method is a no‑op for plain messages)
    const decrypted = await client.e2ee.decryptE2EEMessage(msg);
    console.log("From:", decrypted.from, "Text:", decrypted.text);
  }
}

```

The `decryptE2EEMessage` method inspects `contentType` and `chunks`, performing the appropriate decryption path (text, location, or data) and populating the decrypted fields on the message object.

### Manual Encryption for Storage

For use cases requiring message persistence before sending, encrypt manually using the E2EE class:

```typescript
import { Buffer } from "node:buffer";

const encryptedChunks = await client.e2ee.encryptE2EEMessage(
  "U1234567890abcdef",
  "Stored secret",
);
// `encryptedChunks` is an array of Buffers you can persist.

```

To decrypt stored chunks later, reconstruct a message object and process it:

```typescript
import type { Message } from "@evex/linejs-types";

const storedMsg: Message = {
  from: client.profile!.mid,
  to: "U1234567890abcdef",
  contentType: "NONE",
  chunks: encryptedChunks,
  // …other required fields…
};

const plain = await client.e2ee.decryptE2EEMessage(storedMsg);
console.log(plain.text); // → "Stored secret"

```

## Core Cryptographic Implementation

### Key Exchange and Shared Secrets

The LINEJS E2EE implementation uses **Curve25519** for Diffie-Hellman key exchange. When encrypting, the library reads the sender's private key from local storage and fetches the receiver's public key via `talk.negotiateE2EEPublicKey` for one-to-one chats, or uses cached group key data for group conversations. The shared secret derivation occurs in `encryptE2EEMessage` using the `sharedKey` calculation.

### AEAD Encryption Details

All encrypted messages use **AES-256-GCM** authenticated encryption. The `encryptE2EEMessageV2` method builds Additional Authenticated Data (AAD) incorporating the spec version, content type, and key IDs. The decryption process in `decryptE2EEMessageV2` validates this AAD and uses the sign buffer as the IV for GCM decryption. Cryptographic primitives are provided by Node.js `crypto` and the `tweetnacl` library.

## Summary

- **Automatic encryption** occurs in `TalkService.sendMessage` when setting `e2ee: true` or when the server mandates encryption.
- **Core encryption logic** lives in [`packages/linejs/base/e2ee/mod.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/e2ee/mod.ts), specifically the `encryptE2EEMessage` method which produces five buffer chunks.
- **Decryption handling** uses `E2EE.decryptE2EEMessage` to automatically route text, location, and data messages to the appropriate decryption helpers.
- **Cryptographic foundation** combines Curve25519 key exchange with AES-256-GCM authenticated encryption.
- **Storage scenarios** support manual encryption and decryption for persisting encrypted messages before transmission or after reception.

## Frequently Asked Questions

### How does LINEJS handle E2EE key negotiation automatically?

The `E2EE` class fetches the receiver's public key via `talk.negotiateE2EEPublicKey` when encrypting messages to individual users, and retrieves cached group keys for group chats. The sender's private key is read from local storage, and the shared secret is computed using Curve25519 Diffie-Hellman without requiring manual key management in the calling code.

### What content types support encryption in LINEJS?

The implementation supports **text messages** (contentType "NONE"), **location messages** (contentType "LOCATION"), and generic **data messages**. The `decryptE2EEMessage` method in [`packages/linejs/base/e2ee/mod.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/e2ee/mod.ts) routes each type to specialized handlers: `decryptE2EETextMessage`, `decryptE2EELocationMessage`, or `decryptE2EEDataMessage`.

### Can I decrypt messages stored offline with LINEJS?

Yes. Use `client.e2ee.encryptE2EEMessage` to generate encrypted chunks for storage, then reconstruct a message object with those chunks and call `client.e2ee.decryptE2EEMessage`. The method works identically for network-received and locally-stored messages as long as the chunk array and metadata are preserved.

### Does LINEJS fall back to encryption if the server requires it?

Yes. According to the source in [`packages/linejs/base/service/talk/mod.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/service/talk/mod.ts), if `sendMessage` receives a server error containing "E2EE" and the original request did not specify encryption, the method automatically sets `options.e2ee = true` and retries the request without throwing the error to the caller.