line_webhook 0.1.0 copy "line_webhook: ^0.1.0" to clipboard
line_webhook: ^0.1.0 copied to clipboard

A Dart package for handling LINE Messaging API Webhooks.

line_webhook #

GitHub pub package GitHub Stars

A Dart package for handling LINE Messaging API Webhooks.

日本語

Features #

  • Parse Webhook request bodies from JSON
  • Verify X-Line-Signature headers (HMAC-SHA256)
  • Full coverage of all Webhook event types via sealed classes with exhaustive pattern matching

Installation #

Add to your pubspec.yaml:

dependencies:
  line_webhook: ^0.1.0

Usage #

Signature verification #

Always verify the signature to reject requests not originating from the LINE platform.

import 'package:line_webhook/line_webhook.dart';

bool handleRequest(String body, String signatureHeader) {
  final isValid = verifyWebhookSignature(
    channelSecret: 'YOUR_CHANNEL_SECRET',
    body: body,               // raw request body string
    signature: signatureHeader, // value of X-Line-Signature header
  );

  if (!isValid) {
    // treat as unauthorized, e.g. return 403
    return false;
  }

  // ...
  return true;
}

Parsing the request body #

import 'dart:convert';
import 'package:line_webhook/line_webhook.dart';

void handleWebhook(String rawBody) {
  final json = jsonDecode(rawBody) as Map<String, dynamic>;
  final body = WebhookRequestBody.fromJson(json);

  print('destination: ${body.destination}');

  for (final event in body.events) {
    handleEvent(event);
  }
}

Handling events #

WebhookEvent is a sealed class, so switch expressions are exhaustively checked at compile time.

void handleEvent(WebhookEvent event) {
  switch (event) {
    case MessageEvent():
      handleMessage(event);
    case FollowEvent():
      print('Followed by: ${(event.source as UserSource).userId}');
    case UnfollowEvent():
      print('Blocked');
    case JoinEvent():
      print('Added to group/room');
    case LeaveEvent():
      print('Removed from group/room');
    case PostbackEvent():
      print('Postback: ${event.postback.data}');
    case MemberJoinedEvent():
      print('Member joined');
    case MemberLeftEvent():
      print('Member left');
    case UnsendEvent():
      print('Message unsent: ${event.unsend.messageId}');
    case VideoPlayCompleteEvent():
      print('Video viewed: ${event.videoPlayComplete.trackingId}');
    case BeaconEvent():
      print('Beacon: ${event.beacon.hwid}');
  }
}

Handling messages #

Message is also a sealed class.

void handleMessage(MessageEvent event) {
  switch (event.message) {
    case TextMessage(:final text):
      print('Text: $text');
    case ImageMessage():
      print('Image (ID: ${event.message.id})');
    case VideoMessage(:final duration):
      print('Video (${duration}ms)');
    case AudioMessage(:final duration):
      print('Audio (${duration}ms)');
    case FileMessage(:final fileName, :final fileSize):
      print('File: $fileName ($fileSize bytes)');
    case LocationMessage(:final latitude, :final longitude):
      print('Location: $latitude, $longitude');
    case StickerMessage(:final packageId, :final stickerId):
      print('Sticker: $packageId / $stickerId');
  }
}

Checking the event source #

void checkSource(WebhookEvent event) {
  switch (event.source) {
    case UserSource(:final userId):
      print('1-on-1 chat: $userId');
    case GroupSource(:final groupId, :final userId):
      print('Group: $groupId (user: $userId)');
    case RoomSource(:final roomId, :final userId):
      print('Room: $roomId (user: $userId)');
  }
}

Detecting redelivered Webhooks #

if (event.deliveryContext.isRedelivery) {
  // Use webhookEventId for idempotency checks to avoid duplicate processing
  print('Redelivered event: ${event.webhookEventId}');
}

Complete example with shelf #

import 'dart:convert';
import 'package:line_webhook/line_webhook.dart';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as io;

const channelSecret = 'YOUR_CHANNEL_SECRET';

Handler get app => const Pipeline().addHandler(_handler);

Future<Response> _handler(Request request) async {
  if (request.method != 'POST') {
    return Response(405);
  }

  final body = await request.readAsString();
  final signature = request.headers['x-line-signature'] ?? '';

  if (!verifyWebhookSignature(
    channelSecret: channelSecret,
    body: body,
    signature: signature,
  )) {
    return Response.forbidden('Invalid signature');
  }

  final webhookBody = WebhookRequestBody.fromJson(
    jsonDecode(body) as Map<String, dynamic>,
  );

  for (final event in webhookBody.events) {
    handleEvent(event);
  }

  return Response.ok('OK');
}

void main() async {
  await io.serve(app, 'localhost', 8080);
  print('Server running at http://localhost:8080');
}

Supported events #

Event Class
Message MessageEvent
Unsend UnsendEvent
Follow FollowEvent
Unfollow (block) UnfollowEvent
Join JoinEvent
Leave LeaveEvent
Member joined MemberJoinedEvent
Member left MemberLeftEvent
Postback PostbackEvent
Video play complete VideoPlayCompleteEvent
Beacon BeaconEvent

Supported message types #

TextMessage / ImageMessage / VideoMessage / AudioMessage / FileMessage / LocationMessage / StickerMessage

0
likes
140
points
6
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A Dart package for handling LINE Messaging API Webhooks.

Repository (GitHub)
View/report issues

Topics

#line #webhook #messaging-api #bot

License

MIT (license)

Dependencies

crypto

More

Packages that depend on line_webhook