Create Blockchain Webhooks for Deposits
To receive deposit notifications from Chaingateway you need two parts: a receiver endpoint on your server and a webhook that you create with POST /api/v2/{chain}/webhooks. When a transaction matches the filter of the webhook, Chaingateway sends an HTTP POST request with the transaction data to your endpoint. This tutorial builds both parts for incoming TRC20 deposits on TRON. The same steps work for Ethereum, BNB Smart Chain, Polygon, Arbitrum and Bitcoin. Solana has no webhooks.
Read Webhooks first if you want the concepts and the payload format. You need an API token from the Quickstart.
How do I write a webhook receiver?
Section titled “How do I write a webhook receiver?”A receiver is a server endpoint that accepts HTTP POST requests with a JSON body and answers with status 200. Keep the handler short: store the payload, answer, and do the slow work afterwards in a queue or a background job. The examples below log the payload and acknowledge it.
const express = require('express');const app = express();const port = 3000;
app.use(express.json());
app.post('/webhook', (req, res) => { console.log('Received webhook:', req.body); res.status(200).send('Webhook received');});
app.listen(port, () => { console.log(`Webhook receiver listening at http://localhost:${port}`);});from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/webhook', methods=['POST'])def webhook(): data = request.get_json() print('Received webhook:', data) return jsonify({'status': 'Webhook received'}), 200
if __name__ == '__main__': app.run(port=3000)<?phpif ($_SERVER['REQUEST_METHOD'] === 'POST') { $data = file_get_contents('php://input'); $json = json_decode($data, true); file_put_contents('php://stderr', print_r($json, TRUE)); http_response_code(200); echo json_encode(['status' => 'Webhook received']);}<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class WebhookController extends Controller{ // Register the route in routes/api.php: // Route::post('/webhook', [WebhookController::class, 'handleWebhook']); public function handleWebhook(Request $request) { $data = $request->all(); file_put_contents('php://stderr', print_r($data, TRUE)); return response()->json(['status' => 'Webhook received'], 200); }}Chaingateway can only reach a public URL. During development, expose your local port through a tunnel and use the tunnel’s HTTPS address as the webhook url. Check the signature of every request before you act on it, as Webhooks Security describes.
How do I subscribe to a blockchain event?
Section titled “How do I subscribe to a blockchain event?”Send POST /api/v2/tron/webhooks with the url of your receiver and a filter. The filter decides which transactions trigger a notification.
url(required): the address of your receiver. It must be a valid URL.fromandto: the sender and receiver address, Tron format. At least one of them is required, unless you providecontractaddressandtoken_idinstead.contractaddress: the contract of the token to track.token_id: an NFT token id.type: one ofTRX,TRC10,TRC20,TRC721.reference: your own label of up to 255 characters, for example an order number.
The example watches for TRC20 transfers of one token to one deposit address.
curl --request POST \ --url https://app.chaingateway.io/api/v2/tron/webhooks \ --header 'Accept: application/json' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer YOUR_API_TOKEN' \ --data '{ "url": "https://example.com/webhook", "to": "TUwmZghA7u2GxGxKaxM1mkCmsTF4wHs4vp", "contractaddress": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t", "type": "TRC20", "reference": "order-1042"}'import requests
url = "https://app.chaingateway.io/api/v2/tron/webhooks"payload = { "url": "https://example.com/webhook", "to": "TUwmZghA7u2GxGxKaxM1mkCmsTF4wHs4vp", "contractaddress": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t", "type": "TRC20", "reference": "order-1042"}headers = { 'Accept': 'application/json', 'Content-Type': 'application/json', 'Authorization': 'Bearer YOUR_API_TOKEN'}
response = requests.post(url, json=payload, headers=headers)print(response.json())const axios = require('axios');
const url = 'https://app.chaingateway.io/api/v2/tron/webhooks';const payload = { url: 'https://example.com/webhook', to: 'TUwmZghA7u2GxGxKaxM1mkCmsTF4wHs4vp', contractaddress: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', type: 'TRC20', reference: 'order-1042'};const headers = { 'Accept': 'application/json', 'Content-Type': 'application/json', 'Authorization': 'Bearer YOUR_API_TOKEN'};
axios.post(url, payload, { headers }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error.response?.data ?? error); });<?phprequire 'vendor/autoload.php';
use GuzzleHttp\Client;
$client = new Client();$response = $client->post('https://app.chaingateway.io/api/v2/tron/webhooks', [ 'headers' => [ 'Accept' => 'application/json', 'Authorization' => 'Bearer YOUR_API_TOKEN', ], 'json' => [ 'url' => 'https://example.com/webhook', 'to' => 'TUwmZghA7u2GxGxKaxM1mkCmsTF4wHs4vp', 'contractaddress' => 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', 'type' => 'TRC20', 'reference' => 'order-1042' ]]);
echo $response->getBody();The response has status 201 and the id of the webhook in data.webhook. Keep the id, because you need it to update or delete the webhook.
{ "status": 201, "ok": true, "message": "Webhook created", "data": { "webhook": "9b2b4dcc-1479-4bcd-9b8a-015673e80f46" }}On other chains the path and the type values change. Ethereum and Arbitrum accept ETH, ERC20 and ERC721, Polygon accepts MATIC, ERC20 and ERC721, BNB Smart Chain accepts BNB, BEP20 and BEP721, and Bitcoin takes only url, from and to. Addresses on the EVM chains must match 0x followed by 40 hex characters. To test on a test network, add the header X-Network: testnet to the creation request.
How can I test the receiver before a real deposit arrives?
Section titled “How can I test the receiver before a real deposit arrives?”Send a sample notification to your own endpoint with curl. The body below has the same fields as a real notification. If your receiver logs it and answers 200, the endpoint works. After that, send a small testnet transfer to the watched address to see the real event.
curl --request POST \ --url https://example.com/webhook \ --header 'Content-Type: application/json' \ --data '{ "id": "9d18457f-c71c-4be4-ae7c-ce04e48359ed", "webhook_id": "9b2b4dcc-1479-4bcd-9b8a-015673e80f46", "from": "TTd9qHyjqiUkfTxe3gotbuTMpjU8LEbpkN", "to": "TUwmZghA7u2GxGxKaxM1mkCmsTF4wHs4vp", "blocknumber": "65542464", "datetime": "2024-09-25 14:48:27", "tokenid": null, "type": "TRC20", "contractaddress": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t", "txid": "18f52f2a55b94a6e3705de4abb18e95dfce79970f27b29450705c561e12b6988", "amount": 108935}'What do I do with failed deliveries and old webhooks?
Section titled “What do I do with failed deliveries and old webhooks?”Chaingateway does not retry failed deliveries automatically. GET /api/v2/tron/webhooks/notifications/failed lists them, and POST /api/v2/tron/webhooks/notifications/{id}/retry sends one again. Use GET /api/v2/tron/webhooks to list your webhooks, PUT /api/v2/tron/webhooks/{id} to change one and DELETE /api/v2/tron/webhooks/{id} to remove it. Delete a webhook once it has done its job, for example after the payment for an order has arrived.
Important information
Section titled “Important information”- The receiver must be publicly reachable and accept HTTP POST requests.
- Verify the origin and integrity of each request with the
X-Signatureheader. - Make the receiver idempotent. Use the
txidto detect a notification that you have already processed. - Keep your API token in a secrets store, not in the receiver’s source code.
Sources
Section titled “Sources”- RFC 9110, HTTP Semantics defines the POST method and the status codes used by the receiver.
- TRON TRC20 protocol interface defines the token events behind TRC20 notifications.
- Ethereum token standard EIP-20 defines the same for ERC-20 on the EVM chains.