13 สัญญาณจาก Webhook Events ที่จะปลุกให้ LINE Bot ของคุณตื่นจากภวังค์

Jirawatee
Jirawatee
Jan 11 · 8 min read

Webhook Events คือ event ต่างๆที่เกิดขึ้นกับ LINE Bot(Event trigger) โดยเมื่อ event เกิดขึ้นแล้วจะมีสัญญาณพร้อมกับข้อมูลในรูปแบบที่เป็น JSON วิ่งมาที่ Webhook API ที่เราผูกไว้ใน LINE Developers Console ซึ่ง ณ วันที่ผมเขียนบทความนี้ LINE Bot สามารถรับ Webhook Events ได้ทั้งหมด 13 สัญญาณด้วยกัน

แต่สำหรับนักพัฒนาท่านใดที่หลงเข้ามาอ่านบทความนี้ และยังไม่เคยสร้าง Webhook API สำหรับ LINE Bot มาก่อน แนะนำให้อ่านบทความด้านล่าง โดยเฉพาะหัวข้อที่ 3 เรื่องการ Reply จะได้เห็นภาพว่า Webhook ทำงานอย่างไร

Common Properties

  • type: ประเภทของ event ได้แก่ message, follow, unfollow, join, leave, memberJoined, memberLeft, postback, beacon, accountLink และ things
  • timestamp: เวลาที่ event เกิดขึ้น หน่วยเป็น milliseconds เช่น 1462629479859
  • source: แหล่งที่มาอยู่ในรูปแบบ object ได้แก่ user, group และ room

จาก source ด้านบน เรามาแจกแจงรายละเอียดกันหน่อย ว่าแต่ละตัวแตกต่างกันอย่างไร อะเริ่ม!

source user: บ่งบอกว่ามาจากผู้ใช้สื่อสารกับ Bot แบบ 1:1 ซึ่งเราจะได้ userId ของผู้ใช้คนนั้นๆมา

"source": {
"type": "user",
"userId": "U4af4980629..."
}

source group: บ่งบอกว่ามาจากผู้ใช้สื่อสารกับ Bot ในกลุ่ม ซึ่งเราจะได้ทั้ง groupId และ userId(เฉพาะกรณีผู้ใช้คนนั้นเพิ่ม Bot เป็นเพื่อนแล้ว)

"source": {
"type": "group",
"groupId": "Ca56f94637c...",
"userId": "U4af4980629..." //เฉพาะกรณีผู้ใช้คนนั้นเพิ่ม Bot เป็นเพื่อนแล้ว
}

source room: บ่งบอกว่ามาจากผู้ใช้สื่อสารกับ Bot ในห้อง ซึ่งกรณีนี้เราจะได้ทั้ง roomId และ userId(เฉพาะกรณีผู้ใช้คนนั้นเพิ่ม Bot เราเป็นเพื่อน)

"source": {
"type": "room",
"roomId": "Ra8dbf4673c...",
"userId": "U4af4980629..." //เฉพาะกรณีผู้ใช้คนนั้นเพิ่ม Bot เป็นเพื่อนแล้ว
}

โครงสร้างของ Payload

  • events: ข้อมูลสัญญาณทั้ง 13 แบบ
  • destination: UID ของ Bot

UID ของ Bot จะบอกที่มาว่าผู้ใช้สนทนากับ Bot ตัวไหนอยู่ ทำให้เราสามารถรองรับ Multi-Bot ด้วย API เดียวได้ สะด๊วกสะดวก

{
"events": [{
"type": "[message, follow, unfollow, join, leave, memberJoined, memberLeft, postback, beacon, accountLink, things]",
"source": {
"type": "[user, group, room]",
...
},
"timestamp": 1546848285013,
...
}],
"destination": "U820116ffcbe3f3ca7..."
}

Webhook Events

ใคร deploy เรียบร้อยแล้ว ก็มาเริ่มกันเลย


1. Message Event

  • type:message
  • replyToken: Token ที่ใช้ในการส่งข้อความกลับ
  • message: เป็น object ที่บรรจุข้อความประเภทต่างๆที่ผู้ใช้ส่งมา ได้แก่ Text, Image, Video, Audio, File, Location และ Sticker

จาก Message object ด้านบน เรามาดูรายละเอียดและตัวอย่างของแต่ละประเภทกัน

1.1 Text

  • id: id ของข้อความ
  • type: text
  • text: ข้อความที่ส่งมาเป็นตัวหนังสือ
{
"events": [{
"type": "message",
"replyToken": "50781340b00541cc...",
"source": {
"userId": "U3c28a70ed7c5e7ce2...",
"type": "user"
},
"timestamp": 1546848285013,
"message": {
"type": "text",
"id": "9141452766858",
"text": "Hello, world!"
}
}],
"destination": "U820116ffcbe3f3ca7..."
}

1.2 Image

  • id: id ของข้อความ
  • type: image
  • contentProvider.type: provider ของรูป แบ่งออกเป็น
    -lineกรณีส่งจากผู้ใช้โดยตรง
    -external กรณีส่งจาก LIFF
  • contentProvider.originalContentUrl: URL ของรูป เฉพาะที่ contentProvider.type เป็น external
  • contentProvider.previewImageUrl: URL ของรูปพรีวิว เฉพาะที่ contentProvider.type เป็น external
{
"events": [{
"type": "message",
"replyToken": "2fad91d0e9424bb5...",
"source": {
"userId": "U3c28a70ed7c5e7ce2...",
"type": "user"
},
"timestamp": 1546854929833,
"message": {
"type": "image",
"id": "9141988397354",
"contentProvider": { "type": "line" }
}
}],
"destination": "U820116ffcbe3f3ca71b..."
}

1.3 Video

  • id: id ของข้อความ
  • type: video
  • duration: ความยาวของวิดีโอ หน่วยเป็น milliseconds
  • contentProvider.type: provider ของวิดีโอ แบ่งออกเป็น
    -lineกรณีส่งจากผู้ใช้โดยตรง
    -external กรณีส่งจาก LIFF
  • contentProvider.originalContentUrl: URL ของวิดีโอ เฉพาะที่ contentProvider.type เป็น external
  • contentProvider.previewImageUrl: URL ของรูปพรีวิว เฉพาะที่ contentProvider.type เป็น external
{
"events": [{
"type": "message",
"replyToken": "b8585e2d55ac4d92b...",
"source": {
"userId": "U3c28a70ed7c5e7ce2c...",
"type": "user"
},
"timestamp": 1546859261191,
"message": {
"type": "video",
"id": "9142349548909",
"contentProvider": { "type": "line" },
"duration": 3071
}
}],
"destination": "U820116ffcbe3f3ca71b..."
}

1.4 Audio

  • id: id ของข้อความ
  • type: audio
  • duration: ความยาวของไฟล์เสียง หน่วยเป็น milliseconds
  • contentProvider.type: provider ของไฟล์เสียง แบ่งออกเป็น
    -lineกรณีส่งจากผู้ใช้โดยตรง
    -external กรณีส่งจาก LIFF
  • contentProvider.originalContentUrl: URL ของไฟล์เสียง เฉพาะที่ contentProvider.type เป็น external
{
"events": [{
"type": "message",
"replyToken": "5aa27b4b6299486...",
"source": {
"userId": "U3c28a70ed7c5e7ce...",
"type": "user"
},
"timestamp": 1547094872285,
"message": {
"type": "audio",
"id": "9157571757912",
"contentProvider": { "type": "line" },
"duration": 1590
}
}],
"destination": "U820116ffcbe3f3ca71..."
}

1.5 File

  • id: id ของข้อความ
  • type: file
  • fileName: ชื่อไฟล์
  • fileSize: ขนาดของไฟล์ในหน่วย byte
{
"events": [{
"type": "message",
"replyToken": "b62969124db74697...",
"source": {
"groupId": "C728f1ee876d403e4...",
"userId": "U3c28a70ed7c5e7ce2...",
"type": "group"
},
"timestamp": 1547096682306,
"message": {
"type": "file",
"id": "9157693274393",
"contentProvider": { "type": "line" },
"fileName": "Rub Kwan By Sri",
"fileSize": 841856
}
}],
"destination": "U820116ffcbe3f3ca71..."
}

1.6 Location

  • id: id ของข้อความ
  • type: location
  • title: ชื่อสถานที่
  • address: ที่อยู่
  • latitude: ละติจูด หน่วยเป็นทศนิยม
  • longitude: ลองจิจูด หน่วยเป็นทศนิยม
{
"events": [{
"type": "message",
"replyToken": "baed6d778bb04a24...",
"source": {
"userId": "U3c28a70ed7c5e7ce2...",
"type": "user"
},
"timestamp": 1547102567952,
"message": {
"type": "location",
"id": "9158079495874",
"title": "LINE Thailand",
"address": "Gaysorn Tower Pathum Wan Bangkok 10330 Thailand",
"latitude": 13.745984,
"longitude": 100.540779
}
}],
"destination": "U820116ffcbe3f3ca71..."
}

1.7 Sticker

  • id: id ของข้อความ
  • type: sticker
  • packageId: id ของแพ๊คเกจ
  • stickerId: id ของสติ๊กเกอร์
  • stickerResourceType: ประเภทของสติ๊กเกอร์ (STATIC, ANIMATION, SOUND, ANIMATION_SOUND, POPUP, POPUP_SOUND, NAME_TEXT)
{
"events": [{
"type": "message",
"replyToken": "20c0fb718a564...",
"source": {
"userId": "U3c28a70ed7c5e7...",
"type": "user"
},
"timestamp": 1547103403077,
"message": {
"type": "sticker",
"id": "9158135798213",
"stickerId": "30505571",
"packageId": "10597",
"stickerResourceType": "STATIC"
}
}],
"destination": "U820116ffcbe3f3ca71..."
}

Event ประเภท image, video, audio และ file เราสามารถไปดึง binary ออกมาได้โดยใช้ Get Content API และ ID ของ Message ตามรายละเอียดด้านล่างนี้

Endpoint: https://api.line.me/v2/bot/message/{messageId}/contentMethod: GETHeaders:
- Authorization: Bearer <access_token ที่ได้มา>

2. Follow Event

  • type:follow
  • replyToken: Token ที่ใช้ในการส่งข้อความกลับ
{
"events": [{
"type": "follow",
"replyToken": "31476556789a4365...",
"source": {
"userId": "U3c28a70ed7c5e7ce2...",
"type": "user"
},
"timestamp": 1547104557739
}],
"destination": "U820116ffcbe3f3ca71..."
}

3. Unfollow Event

  • type:unfollow
{
"events": [{
"type": "unfollow",
"source": {
"userId": "U3c28a70ed7c5e7ce2...",
"type": "user"
},
"timestamp": 1547106290376
}],
"destination": "U820116ffcbe3f3ca71..."
}

unfollow payload จะไม่มี replyToken แนบมา(ก็เค้า unfollow แล้ว อย่าไปยิงเค้าเลย) แต่จะมีประโยชน์กับเราตรงที่ ถ้าเราเก็บ userId ใน database เราสามารถ flag ได้ว่า userId ไหน ไม่ active แล้ว เพื่อที่ต้อง push หรือ multicast จะได้เลือกเฉพาะผู้ใช้ที่ยัง active อยู่


4. Join Event

  • type:join
  • replyToken: Token ที่ใช้ในการส่งข้อความกลับ
{
"events": [{
"type": "join",
"replyToken": "3d41b964299c48efb...",
"source": {
"groupId": "C728f1ee876d403e46...",
"type": "group"
},
"timestamp": 1547108059564
}],
"destination": "U820116ffcbe3f3ca71..."
}

5. Leave Event

  • type:leave
{
"events": [{
"type": "leave",
"source": {
"groupId": "C728f1ee876d403e46...",
"type": "group"
},
"timestamp": 1547108059564
}],
"destination": "U820116ffcbe3f3ca71..."
}

leave payload จะไม่มี replyToken แนบมา(ก็เค้าเตะ Bot ออกจากกลุ่มแล้ว) แต่จะมีประโยชน์กับเราตรงที่ ถ้าเราเก็บ groupId ใน database เราสามารถ flag ได้ว่า groupId ไหน ไม่ active แล้ว เพื่อที่ต้อง push หรือ multicast จะได้เลือกเฉพาะ group หรือ room ที่ยัง active อยู่


6. Member Join Event

  • type:memberJoined
  • replyToken: Token ที่ใช้ในการส่งข้อความกลับ
  • joined.members: object ข้อมูลของผู้ใช้ใหม่ที่เพิ่งเข้าร่วม group หรือ room
{
"events": [{
"type": "memberJoined",
"replyToken": "342e5baf6d2342...",
"source": {
"groupId": "C728f1ee876d403...",
"type": "group"
},
"timestamp": 1547117737398,
"joined": {
"members": [{
"userId": "Ua0e8dd654eeb567...",
"type": "user"
}]
}
}],
"destination": "U820116ffcbe3f3ca71..."
}

7. Member Leave Event

  • type:memberLeft
  • left.members: object ข้อมูลของผู้ใช้ที่ออกจาก group หรือ room
{
"events": [{
"type": "memberLeft",
"source": {
"groupId": "C728f1ee876d403e4...",
"type": "group"
},
"timestamp": 1547129903218,
"left": {
"members": [{
"userId": "Ua0e8dd654eeb5...",
"type": "user"
}]
}
}],
"destination": "U820116ffcbe3f3ca71..."
}

8. Postback Event

  • type:postback
  • replyToken: Token ที่ใช้ในการส่งข้อความกลับ
  • postback.data: ข้อมูลที่ผู้ใช้แนบมา
  • postback.params: ข้อมูลวันเวลา ซึ่งมีได้ 3 รูปแบบ
    - date วันที่ที่ผู้ใช้เลือก
    - time เวลาที่ผู้ใช้เลือก
    - datetime วันและเวลาที่ผู้ใช้เลือก
    ทั้งนี้ขึ้นอยู่กับการตั้งค่าใน datetime picker action ซึ่ง property ตัวนี้จะมีเฉพาะกรณีที่มาจาก datetime picker action
{
"events": [{
"type": "postback",
"replyToken": "8ae8a2f124134d3db1...",
"source": {
"userId": "U3c28a70ed7c5e7ce2c9...",
"type": "user"
},
"timestamp": 1547131234426,
"postback": {
"data": "action=buy&itemid=123"
}
}],
"destination": "U820116ffcbe3f3ca71..."
}

9. Beacon Event

  • type:beacon
  • replyToken: Token ที่ใช้ในการส่งข้อความกลับ
  • beacon.hwid: Hardware ID ของตัว beacon
  • beacon.type: ประเภทของสัญญาณ beacon ซึ่งมี 3 รูปแบบ
    - enter เข้าสู่ระยะทำการ
    - leave ออกจากระยะทำการ
    - banner เมื่อตัว beacon สามารถ detect ผู้ใช้ในบริเวณได้
  • beacon.dm: ข้อความซึ่งสามารถตั้งค่ามากับ beacon
{
"events": [{
"type": "beacon",
"replyToken": "d6274364652f4543a9...",
"source": {
"userId": "U3c28a70ed7c5e7ce2c9...",
"type": "user"
},
"timestamp": 1547133415314,
"beacon": {
"hwid": "011ee00bf4",
"dm": "00000000000000",
"type": "enter"
}
}],
"destination": "U820116ffcbe3f3ca71..."
}

10. Device Link Event

  • type:things
  • replyToken: Token ที่ใช้ในการส่งข้อความกลับ
  • things.deviceId: Device ID ของ LINE Things device
  • things.type: link
{
"events": [{
"type": "things",
"replyToken": "d6274364652f4543a9...",
"timestamp": 1462629479859,
"source": {
"type": "user",
"userId": "U3c28a70ed7c5e7ce2c9..."
},
"things": {
"deviceId": "t2c449c9d1...",
"type": "link"
}
}],
"destination": "U820116ffcbe3f3ca71..."
}

11. Device Unlink Event

  • type:things
  • replyToken: Token ที่ใช้ในการส่งข้อความกลับ
  • things.deviceId: Device ID ของ LINE Things device
  • things.type: unlink
{
"events": [{
"type": "things",
"replyToken": "d6274364652f4543a9...",
"timestamp": 1462629479859,
"source": {
"type": "user",
"userId": "U3c28a70ed7c5e7ce2c9..."
},
"things": {
"deviceId": "t2c449c9d1...",
"type": "unlink"
}
}],
"destination": "U820116ffcbe3f3ca71..."
}

12. Scenario Execution Event

  • type:things
  • replyToken: Token ที่ใช้ในการส่งข้อความกลับ
  • things.deviceId: Device ID ของ LINE Things device
  • things.type: scenarioResult
  • things.result.scenarioId: ID ของ Scenario ที่ถูก execute
  • things.result.revision: เลข Revision ใน Scenario set
  • things.result.startTime: Timestamp ที่เริ่ม execute
  • things.result.endTime: Timestamp เมื่อ execute ตัว Scenario เสร็จ
  • things.result.resultCode: Status หลังการ execute ตัว Scenario (success, gatt_error, runtime_error)
  • things.result.bleNotificationPayload: ข้อมูล binary ที่เข้ารหัสด้วย Base64 ซึ่งสามารถแนบมาจาก device กรณีที่ register ตัว Scenario set เป็นแบบ trigger.type = BLE_NOTIFICATION
  • things.result.actionResults: Array ของผลลัพธ์จาก action หากเราได้ระบุให้ device ทำงาน ซึ่งอยู่ในขั้นตอน register ตัว scenario set
  • things.result.actionResults[].type: void, binary
  • things.result.actionResults[].data: ข้อมูล binary ที่เข้ารหัสด้วย Base64 ซึ่งจะมาเฉพาะกรณีที่ type ด้านบนเป็น binary เท่านั้น
  • things.result.errorReason: ข้อความ error กรณีที่ status เป็น gatt_error หรือ runtime_error
{
"events": [{
"type": "things",
"replyToken": "d6274364652f4543a9...",
"source": {
"userId": "U3c28a70ed7c5e7ce2c9...",
"type": "user"
},
"timestamp": 1547817848122,
"things": {
"type": "scenarioResult",
"deviceId": "t2c449c9d1...",
"result": {
"scenarioId": "XXX",
"revision": 2,
"startTime": 1547817845950,
"endTime": 1547817845952,
"resultCode": "success",
"bleNotificationPayload": "AQ==",
"actionResults": [{
"type": "binary",
"data": "/w=="
}]
}
}
}],
"destination": "U820116ffcbe3f3ca71..."
}

13. Acount Link Event

  • type:accountLink
  • replyToken: Token ที่ใช้ในการส่งข้อความกลับ
  • link: object ที่มีผลลัพธ์ของการเชื่อมต่อ และ nonce
{
"events": [{
"type": "accountLink",
"replyToken": "b60d432864f44d079s...",
"source": {
"userId": "U3c28a70ed7c5e7ce2c9...",
"type": "user"
},
"timestamp": 1513669370317,
"link": {
"result": "ok",
"nonce": "xxxxxxxxxxxxxxx"
}
}],
"destination": "U820116ffcbe3f3ca71..."
}

ส่วนตัวผมยังไม่ได้ลองพัฒนาบริการให้เกิด Account Link Event หากใครลองแล้วก็มาเล่าสู่กันฟังหน่อยนะครับ


สรุป

และจากการที่ผมเขียนบทความนี้ ก็ทำให้ผมเพิ่งรู้ว่า LIFF มันส่ง message ได้มากกว่า text แล้วนะ ตอนนี้เราสามารถส่ง text, image, video, audio, location และ template message กลับเข้าไปในห้องแชทได้แล้ว สามารถสังเกตุได้จาก property ที่ชื่อ contentProvider.type ซึ่งถ้าค่าเป็น external แล้ว นั่นหละมาจาก LIFF เลย

สุดท้ายนี้ ก็หวังว่านักพัฒนาจะเกิดไอเดียในการพัฒนา LINE Bot ให้ตอบสนองกับ event ต่างๆ และทำให้ประสบการณ์การใช้งาน LINE Bot ของคุณดีมากขึ้น สนุกขึ้น สำหรับวันนี้ขอตัวลาไปก่อน แล้วพบกันใหม่บทความหน้าครับชาว #LINEDEVTH

LINE Developers Thailand

Closing the distance. Our mission is to bring people, information and services closer together

Jirawatee

Written by

Jirawatee

Technology Evangelist at LINE Thailand / Google Developer Expert - 🔥Firebase

LINE Developers Thailand

Closing the distance. Our mission is to bring people, information and services closer together

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade