Subscribing to Events (Webhook)

Webhook을 이용해 어떻게 Integration에서 수집한 데이터를 받아올 수 있는지 알아봅시다.

Webhook 이란?

Webhook은 Integration에서 블록체인 데이터를 수집했을 때 데이터를 HTTP 호출 방식으로 전달하는 개념입니다. Webhook 방식으로 데이터를 받기 위해서 서버 URL을 제공해야 합니다. Henesis에서는 블록체인 데이터가 수집되었을 때 제공된 URL에 HTTP 호출을 합니다. 지속적으로 데이터를 폴링(Polling)하는 것보다 새롭게 데이터가 생성되었을 때 데이터를 푸시(Push)하는 Webhook 방식이 리소스 사용에 더 효율적입니다.

ngrok 세팅

ngrok은 손쉽게 외부에 노출할 수 있는 URL을 만들어주는 서비스입니다. 해당 URL로 들어온 트래픽을 내부 네트워크에 포워딩할 수 있습니다. 고정 IP가 없는 로컬 환경에서 간단하게 webhook이 잘 동작하는지 확인 때에 유용합니다.

$ unzip /path/to/ngrok.zip
$ ./ngrok authtoken <YOUR_AUTH_TOKEN>
  • 로컬 호스트(localhost)의 3000 포트를 External IP로 노출 시킵니다.

$ ./ngrok http 3000
  • 다음과 같은 화면이 출력되는 지 확인합니다. 화면에 보이는 http://xxxx.ngrok.io 를 클립보드에 복사합니다

ngrok by @inconshreveable (Ctrl+C to quit)
Session Status online
Account (Plan: Free)
Version 2.3.29
Region United States (us)
Web Interface http://127.0.0.1:4040
Forwarding http://xxxx.ngrok.io -> http://localhost:3000
Forwarding https://xxxx.ngrok.io -> http://localhost:3000
Connections ttl opn rt1 rt5 p50 p90
0 0 0.00 0.00 0.00 0.00

서버 구현

Express를 이용해 Node 기반의 Webhook 서버를 아래와 같이 만듭니다.

$ npm install express --save
server.js
const express = require('express');
const app = express();
// port는 ngrok에서 설정한 port와 동일해야합니다.
const port = 3000;
app.use(express.json());
app.post('/webhook', (req, res) => {
console.log(JSON.stringify(req.body, null, ' '));
res.send('Received')
});
app.listen(port, () => {
console.log(`Application listening on port ${port}!`)
});

server.js 를 이용해 Henesis가 HTTP POST로 호출할 /webhook URL을 제공할 수 있습니다.

henesis.yaml 설정 및 배포

henesis.yaml에 대한 자세한 정보는 Deploy Integration 챕터를 참고해주세요. 여기서는 webhook을 위해 세팅해야 할 정보만 다시 한번 살펴보겠습니다.

henesis.yaml
...
provider:
type: webhook
url: http://xxxx.ngrok.io/api/webhook
method: POST
headers:
Authorization: 'Bearer YOUR-OWN-TOKEN'
  • typewebhook으로 설정해주세요.

  • webhook을 받을 urlmethod를 설정해주세요.

  • HTTP header가 필요하다면 headers에 입력해주세요. (ex. Authorization)

작성한 henesis.yaml을 CLI를 통해 배포해주세요.

실행

앞서 작성한 server.js 로 Node 서버를 실행해봅니다.

$ node server.js

그 다음, 콘솔창에서 블록체인 메세지가 올바르게 출력되는지 확인합니다.

네트워크 이상(와이파이 문제 등)으로 인해 중복된 메세지가 전달될 수 있습니다. 따라서 전달받은 데이터에 대한 인덱스(메세지ID 혹은 블록 넘버)를 저장하여 중복 데이터에 대한 처리를 반드시 해주어야 합니다.

Webhook으로 구독하는 메세지의 포멧은 아래와 같습니다.

Ethereum
Klaytn
Ethereum
{
"events": [
{
"contractAddress": "0x50965254bb66c78556a18249e11a48e2dcf1d250",
"contractName": "TestContract",
"eventName": "Register(bytes32,uint256)",
"data": [
{
"name": "id",
"value": "0x1719121276170000000000000000000000000000000000000000000000000000",
"type": "bytes32"
},
{ "name": "expireTime", "value": "191219", "type": "uint256" }
],
"raw": {
"topics": [
"0xd5fa0e9a716b3ec4895a48223ad309e2d3fa5e27f04d8dc9b3c33cc738a50eb0"
],
"data": "0x17191212761700000000000000000000000000000000000000000000000000000x000000000000000000000000000000000000000000000000000000000002eaf3",
},
"transaction": {
"blockHash": "0x3812ea8f0f194e1524d8c6debbeb12d15c05982bb57aacb0c3fd329d0ed54a9a",
"blockNumber": 9093483,
"contractAddress": null,
"cumulativeGasUsed": 885628,
"from": "0xB5e14B116EBD01133C5D5f0d0ab5f24E395439a3",
"gasUsed": 44086,
"status": true,
"to": "0x50965254bb66C78556a18249E11a48E2DCf1D250",
"transactionIndex": 18,
"gas": 6000000,
"gasPrice": "8000000000",
"hash": "0x5c17099eedecd11aa47b1c66475da86ee43be8a118912607be19000c93d2edf5",
"input": "0xff3617f81719121276170000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002eaf3",
"nonce": 5086,
"r": "0x5c1500e441ddaaa119683182c1d346a0a66f957feb08728d02656f97f2571bfd",
"s": "0x1c19fc7a9b240afcec64acc61d6b6a694af9886aa9120accc3c448858090f67a",
"v": "0x26",
"value": "0"
}
}
],
"blockMeta": {
"platform": "ethereum",
"network": "mainnet",
"blockNumber": 9093483,
"blockHeader": {
"difficulty": "2547810825060025",
"extraData": "0x7070796520e4b883e5bda9e7a59ee4bb99e9b1bc",
"gasLimit": 9969200,
"gasUsed": 3452439,
"hash": "0x3812ea8f0f194e1524d8c6debbeb12d15c05982bb57aacb0c3fd329d0ed54a9a",
"logsBloom": "0x84088100111003a006800200000008102000d0ba05082000808100418080106100020202848250003010000004024b0432000002080c2c102002800571000d00400010084412000342c045aa002842080002290c004c0040880a0000a82234410100210a1210408028641102004c4e28002003211000080800000219000030a030010000401690a0011000410c00840030800004000080040800141008340841040489080000900c640090841440a9c100042005a052084200870c0806200160c05801528900404c009842002024a0284414400000003008390840a012443a28880064810c820008164048800814800000a44001218440002040008000520040",
"miner": "0x829BD824B016326A401d083B33D092293333A830",
"mixHash": "0xcfd9e44b717f0f6f04690c684ab3013a7f872d435ad34d73be6a163cddae0225",
"nonce": "0x5308ff300140b240",
"number": 9093483,
"parentHash": "0x3d13e75fce4de5bd92b859a18cb1c10324f57916139e7e6dca193c14d3d67328",
"receiptsRoot": "0x22600ecec69d4756e82bc20d16c877c53c39c637baa7a44dbe27d8ac6e57531f",
"sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"size": 12452,
"stateRoot": "0xff321601808d9d9452cb0149a1d0f3427c1531e1dada276f20ccb64c065a68f9",
"timestamp": 1576140150,
"totalDifficulty": "13250222900598223619206",
"transactions": [
"0x7171ef3894af077e32bcf1ee2c5800753ea4f4d09c2172f0bcad81d66a8dbf6e",
...
],
"transactionsRoot": "0x2fae63d1ecbedeabf0c1a37a64a55350e41b207dc31e7571e98784c9ff619f73",
"uncles": []
}
},
"userMeta": { "integrationId": "test-kkymb", "userId": 1 },
}
Klaytn
{
"events": [
{
"contractAddress": "0x0fde1e1be873ec1d08e260408a116cb49fe6c780",
"contractName": "TestContract",
"eventName": "Plus2Depth(address,address)",
"data": [
{
"name": "caller",
"value": "0x0fde1E1BE873EC1D08e260408A116cB49FE6C780",
"type": "address"
},
{
"name": "callee",
"value": "0xa1C25a35A9094c6D58a5457F488Ee46e93b0bBC2",
"type": "address"
}
],
"raw": {
"topics": [
"0x37eef058e3fac3cfd57f54e4ec6404e18252d961ac70598eb66635d37fdc50c6"
],
"data": "0x0000000000000000000000000fde1e1be873ec1d08e260408a116cb49fe6c780000000000000000000000000a1c25a35a9094c6d58a5457f488ee46e93b0bbc2"
},
"transaction": {
"blockHash": "0x6198213d42677d54b4bf5e3cd812bf6dca0960abff9bff32d4efa4b78c3aaaaa",
"blockNumber": 14592584,
"contractAddress": null,
"from": "0xd4c57f597007d79800d0a0ccf9eb3335deeb60dc",
"gas": 1511424,
"gasPrice": "25000000000",
"gasUsed": 45902,
"input": "0x432839ad00000000000000000000000091fb1b6ae729e51054e1ee53f78ca972dbe783f00000000000000000000000000fde1e1be873ec1d08e260408a116cb49fe6c780000000000000000000000000a1c25a35a9094c6d58a5457f488ee46e93b0bbc2",
"nonce": 3220,
"senderTxHash": "0xebaf8334a4886868aecf3e6264220650f8539e942ccafb9f6ea641944c5bcbb3",
"signatures": [
{
"V": "0x7f5",
"R": "0xe979f9ffcf7fe4eafbdfbcd9f8caa114c0269171a74622958fcf9c1a9fe8a175",
"S": "0x35aafe2498c67751f97a5e7e1af18255df2409aa6920d1765dbb189176f9538d"
}
],
"status": true,
"to": "0x35a1a5243bfe914d36c8bf7cb4f380fa2ecfb271",
"transactionIndex": 1,
"type": "TxTypeLegacyTransaction",
"typeInt": 0,
"value": "0",
"hash": "0xebaf8334a4886868aecf3e6264220650f8539e942ccafb9f6ea641944c5bcbb3"
}
}
],
"blockMeta": {
"platform": "klaytn",
"network": "baobab",
"blockNumber": 14592584,
"blockHeader": {
"blockscore": "0x1",
"extraData": "0xd883010200846b6c617988676f312e31322e35856c696e757800000000000000f90164f85494571e53df607be97431a5bbefca1dffe5aef56f4d945cb1a7dccbd0dc446e3640898ede8820368554c89499fb17d324fa0e07f23b49d09028ac0919414db694b74ff9dea397fe9e231df545eb53fe2adf776cb2b8416ed35245e302ed3dbd9ae4ea118b15d025674312831cb40ce2a1064f7404210b084d20520160f2b8236927c50ed85f0602ceaf458250a39d2aa2b9f8b66acd7500f8c9b8417eb83ae6c6dcb99f514797d4efadc87811df4ae948f28542c1209b458ed7f2427a34d1c96adfd15a3cb9e7b777a32cc64f6ff077bd57d14d16dc79b92ed8062500b84175f171ee407b976d0be860023719df19f76950cb4556371c5faf9f87feb85eaf7290495a6d22a2fe9677ce8e8f7f331d4bc1309c35a14f3155d9913df4672a1900b8410a1bebe0a42ebcfdb629a09154675f6ab7d167363f0fb831ed0dc5c564c4fc330f14b89571823f1718e57417e434a94f0cd2c65aeddb3a3d5b6a210ffcde2d3901",
"gasUsed": 112512,
"governanceData": "0x",
"hash": "0x6198213d42677d54b4bf5e3cd812bf6dca0960abff9bff32d4efa4b78c3aaaaa",
"logsBloom": "0x00000002010000000000000000000000000000000000000040000120000000000000000800000000000000000000000000000080000000000060000000000000000000000000000000080000000402000000000000000000010000000000000000000000000000000000000000000000000000000000000000004000000000000000008000002000000000000000000000000000000040000000000000000000000000000000000010000000000020000000000000000080000000000000000000800000000000000000000000000000000000000000040000000000000000000000000000000000000000000008000000000400000000002800000000000000",
"number": 14592584,
"parentHash": "0x70c6dcc0c0470d3c25a5307a0e7181965a0775a8f32cf4c8b33a5745c31a78b8",
"receiptsRoot": "0x076c1617348fcbfb3192db8a876bf63d41d482a801f1b0e42c942e6d574cf546",
"reward": "0xa86fd667c6a340c53cc5d796ba84dbe1f29cb2f7",
"size": 1434,
"stateRoot": "0xd1b43cb82e7e7c05703b8f2d8167148d9ae957d234146d5ddcf464578503636f",
"timestamp": 1576139955,
"timestampFoS": "0x2",
"totalBlockScore": "0xdeaa49",
"transactions": [
"0x58cacec3fa903a7d1ef6d7d5f517034f6a88f785f263a3db745477f15576ec87",
"0xebaf8334a4886868aecf3e6264220650f8539e942ccafb9f6ea641944c5bcbb3"
],
"transactionsRoot": "0xd5e96da48067d319255faa356eba28672e3e867589f563a22e373a5e1c3014e0",
"voteData": "0x"
}
},
"userMeta": { "integrationId": "test-absqf", "userId": 123 }
}