Penyiapan Webhook Blockchain: Tanda Tangan, Deduplikasi, dan Pemutaran Ulang
Buat langganan alamat melalui HTTP, verifikasi tanda tangan raw-body, deduplikasi ID peristiwa, dan pulihkan kecocokan yang dipertahankan atau blok yang hilang.
Pantau alamat dompet EVM dan terima transfer native, transfer token, dan log kontrak yang cocok di endpoint HTTPS Anda untuk notifikasi aktivitas dompet atau pemantauan peristiwa smart contract. Pengembang dan agen AI menggunakan API langganan HTTP yang sama. Untuk notifikasi pembayaran ERC-20 USDT / USDC, ikuti penerima pembayaran stablecoin.
Tugas yang dibantu panduan ini
- Terima aktivitas alamat dompet dengan membuat langganan terautentikasi, menambahkan alamat yang dipantau, dan memverifikasi peristiwa yang masuk.
- Pantau log kontrak yang cocok dengan memeriksa peristiwa
loguntuk alamat yang dipantau dan memfilteraddress,topics, dandatadi penerima Anda. - Pulihkan pengiriman yang terputus dengan memeriksa kemajuan langganan dan memutar ulang kecocokan yang dipertahankan, lalu melakukan backfill celah di luar jendela pemutaran ulang (replay).
Sebuah langganan menggabungkan satu URL penerima HTTPS, rahasia penandatanganan (signing secret), alamat-alamat EVM yang dipantau, dan objek chains yang diperlukan. Alamat-alamat berlaku untuk setiap rantai dalam objek tersebut. Gunakan API dengan header x-api-key; setiap kunci aktif di akun Anda dapat mengelola semua langganannya. Dapatkan API key sebelum memulai. Push OpenAPI mencantumkan setiap operasi dan skema webhook.
Hubungkan aktivitas alamat dompet
- Deploy penerima yang memverifikasi body permintaan asli, menyimpan peristiwa secara persisten berdasarkan
id, dan mengonfirmasi penerimaannya dalam waktu 10 detik. - Baca
GET /v1/push/chains, lalu buat langganan dengan URL HTTPS Anda dan rantai yang dipilih. Simpaniddansecretyang dikembalikan. - Tambahkan alamat dompet. Tunggu hingga
applied_version >= change_versiondan catatapplied_from_blockdari setiap rantai; pencocokan dimulai dari sana. - Tangani transfer dan log, dan pulihkan celah atau blok yang diganti. Filter kontrak token, penerima, dan jumlah integer sebelum menggunakan notifikasi dalam pemrosesan pembayaran.
Pilih Webhook, WebSocket, atau polling
- Webhook mengirimkan peristiwa alamat yang dipantau ke penerima HTTPS, dengan percobaan ulang pengiriman dan pemutaran ulang kecocokan yang dipertahankan.
- WebSocket mengalirkan
newHeadsdanlogsyang difilter melalui koneksi persisten. Hubungkan kembali, berlangganan ulang, dan kueri blok yang terlewat setelah terputus. - Polling mengueri
eth_getLogsdalam rentang blok terbatas dengan kursor Anda sendiri; gunakan untuk memantau pembayaran atau melakukan backfill log yang hilang.
Periksa ws dan subscriptions di GET /v1/chains untuk dukungan WebSocket. Jika ws bernilai false, Webhook alamat masih menjadi opsi jika rantai tersebut muncul dalam daftar GET /v1/push/chains terautentikasi. Dukungan RPC saja tidak membuktikan dukungan Push.
Kapasitas alamat
Layanan mandiri mendukung hingga 1.000.000 alamat per langganan dan tersedia saat pendaftaran. Satu langganan mencakup beberapa rantai dengan satu URL penerima. Kapasitas enterprise mendukung 10.000.000 / 100.000.000 alamat per langganan; hubungi kami untuk mengaktifkannya. Pengembang dan agen AI memiliki opsi kapasitas dan harga yang sama. Kedua tingkat menggunakan tarif hari-alamat dan peristiwa terkirim yang sama yang ditampilkan di harga.
Buat langganan
Baca GET /v1/push/chains untuk melihat rantai yang tersedia serta jumlah konfirmasi minimum, default, dan maksimumnya. Sebuah blok dirilis ketika head - block + 1 >= confirmations. Setiap rantai dapat menggunakan default-nya dengan menyediakan {}. Setidaknya satu rantai diperlukan; rantai baru tidak secara otomatis bergabung dengan langganan yang sudah ada.
Simpan contoh berikut sebagai create.json, mengganti URL dengan penerima Anda dan memilih rantai dari daftar rantai. URL harus menggunakan HTTPS pada port 443, sebuah hostname alih-alih IP literal, dan tidak mengandung informasi pengguna atau fragmen.
{
"url": "https://hooks.example.com/push",
"chains": {
"bsc_mainnet": {
"confirmations": 1
},
"base_mainnet": {}
}
}Tetapkan BLOCKVECTRA_API_KEY di lingkungan Anda, lalu jalankan:
PUSH_URL='https://api.blockvectra.com/v1/push'
curl --fail-with-body -sS "$PUSH_URL/chains" \
-H "x-api-key: $BLOCKVECTRA_API_KEY"
curl --fail-with-body -sS "$PUSH_URL/subscriptions" \
-H "x-api-key: $BLOCKVECTRA_API_KEY" \
-H 'Content-Type: application/json' \
-d @create.json > subscription.jsonPembuatan yang berhasil mengembalikan HTTP 201 dan langganan online tanpa alamat. Simpan id numerik dan secret-nya dengan aman. Rahasia hanya dikembalikan saat pembuatan atau POST /subscriptions/{subscription_id}/rotate-secret; rotasi segera berlaku di semua rantai, tanpa tumpang tindih. Tidak ada pesan pengujian yang dikirim.
Tambahkan dan buat daftar alamat
Simpan batch alamat sebagai addresses.json, mengganti alamat contoh dengan alamat yang Anda pantau:
{
"addresses": [
"0x9725db73f2cd8657f3e1841e5689f210ee54a92d",
"0x99d47bB552ae095159C251836De6A5d524076872"
]
}Tetapkan SUBSCRIPTION_ID ke ID langganan yang dikembalikan:
curl --fail-with-body -sS "$PUSH_URL/subscriptions/$SUBSCRIPTION_ID/addresses/add" \
-H "x-api-key: $BLOCKVECTRA_API_KEY" \
-H 'Content-Type: application/json' -d @addresses.json
curl --fail-with-body -sS "$PUSH_URL/subscriptions/$SUBSCRIPTION_ID/addresses" \
-H "x-api-key: $BLOCKVECTRA_API_KEY"
curl --fail-with-body -sS "$PUSH_URL/subscriptions/$SUBSCRIPTION_ID" \
-H "x-api-key: $BLOCKVECTRA_API_KEY"Setiap panggilan penambahan menerima paling banyak 10.000 alamat. Alamat input berupa huruf kecil atau huruf campuran EIP-55 yang valid; input yang tidak valid akan menolak seluruh batch. Alamat yang berulang dihitung sebagai unchanged, sehingga mengirim ulang permintaan penambahan yang sama aman dilakukan. Daftar alamat menggunakan limit dan page_token; next_page_token: null menandai halaman terakhir.
Menambahkan alamat mengembalikan change_version. Lakukan polling atau periksa GET /subscriptions/{subscription_id} hingga applied_version >= change_version; perubahan biasanya membutuhkan waktu sekitar 1 detik untuk diterapkan. applied_from_block setiap rantai mengidentifikasi blok efektif tempat transaksi dan log on-chain mulai dicocokkan. Alamat baru tidak dicocokkan secara retroaktif.
Membuat langganan mengembalikan HTTP 201 untuk mengonfirmasi bahwa sumber daya langganan telah dibuat; HTTP 201 tidak berarti penerima Anda telah menerima push webhook apa pun. Platform tidak mengirimkan pesan verifikasi atau uji coba saat pembuatan atau pendaftaran alamat. Anda harus menunggu hingga aktivitas on-chain yang cocok terjadi pada alamat dan rantai yang dipantau untuk memverifikasi pengiriman di penerima Anda.
Format peristiwa
Setiap POST memiliki type: push.events, created_at, dan data. data berisi subscription_id, satu chain, complete_through_block, dan events. Catat kemajuan per rantai: sebuah blok dapat mencakup beberapa pesan, sehingga nomor blok peristiwa individual bukanlah penanda penyelesaian. Setiap pesan berisi paling banyak 1.000 peristiwa, 1 MiB, dan 50 blok.
{
"type": "push.events",
"created_at": "2026-10-02T03:00:05Z",
"data": {
"subscription_id": 48213,
"chain": "bsc_mainnet",
"complete_through_block": 64000121,
"events": [
{
"id": "evt_payvsqb6ogymhmehrs2wl5xcky",
"type": "native.transfer",
"ref": "eip155:56:0x7b19944dc683c33e8dedba259cb6939f7271f70f2eeb6ca5e456cccb57cc1eff:tx",
"from": "0xe0a2100d7dad33f70c4bb765323cb96b2400c844",
"to": "0x9725db73f2cd8657f3e1841e5689f210ee54a92d",
"amount": "150000000000000000",
"block_number": 64000120,
"block_hash": "0x327892a3e5699a43981f0fbcc5e490628641d92c040eb0429fb550ba3a73c3bf",
"block_timestamp": "2026-10-02T03:00:00Z",
"tx_hash": "0x7b19944dc683c33e8dedba259cb6939f7271f70f2eeb6ca5e456cccb57cc1eff",
"tx_index": 3,
"matched": [
{
"address": "0x9725db73f2cd8657f3e1841e5689f210ee54a92d",
"role": "to"
}
]
},
{
"id": "evt_lgcdattb6l2k3ejuhe4mtdljkm",
"type": "token.transfer",
"ref": "eip155:56:0x3be3448e6f54ebfa928d5997a0cb2d5e9d3186c5ebfa293ad45b7edc72483a76:7",
"standard": "erc20",
"token": "0x55d398326f99059ff775485246999027b3197955",
"from": "0x0f94e5283c41c29a8f4dff8c17f68bdfb59f07df",
"to": "0x99d47bb552ae095159c251836de6a5d524076872",
"token_id": null,
"amount": "25000000000000000000",
"batch_index": null,
"block_number": 64000121,
"block_hash": "0x6a8146159162f182c091d17eac7d03e95dc92ce80de704c958ca2528306aff15",
"block_timestamp": "2026-10-02T03:00:00Z",
"tx_hash": "0x3be3448e6f54ebfa928d5997a0cb2d5e9d3186c5ebfa293ad45b7edc72483a76",
"tx_index": 5,
"log_index": 7,
"matched": [
{
"address": "0x99d47bb552ae095159c251836de6a5d524076872",
"role": "to"
}
]
},
{
"id": "evt_sgliw3ficdf6gaa6zzx4ew6vni",
"type": "log",
"ref": "eip155:56:0x3be3448e6f54ebfa928d5997a0cb2d5e9d3186c5ebfa293ad45b7edc72483a76:8",
"address": "0xb54ffbe723264b84cf74947127a6914cf87fc593",
"topics": [
"0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925",
"0x00000000000000000000000099d47bb552ae095159c251836de6a5d524076872",
"0x000000000000000000000000b54ffbe723264b84cf74947127a6914cf87fc593"
],
"data": "0x0000000000000000000000000000000000000000000000000000000000000000",
"block_number": 64000121,
"block_hash": "0x6a8146159162f182c091d17eac7d03e95dc92ce80de704c958ca2528306aff15",
"block_timestamp": "2026-10-02T03:00:00Z",
"tx_hash": "0x3be3448e6f54ebfa928d5997a0cb2d5e9d3186c5ebfa293ad45b7edc72483a76",
"tx_index": 5,
"log_index": 8,
"matched": [
{
"address": "0x99d47bb552ae095159c251836de6a5d524076872",
"role": "topic1"
}
]
}
]
}
}| Jenis peristiwa | Yang harus ditangani |
|---|---|
native.transfer | Transfer nilai native tingkat teratas yang berhasil yang melibatkan alamat yang dipantau; amount adalah string desimal integer. Transfer native internal tidak termasuk. |
token.transfer | Transfer ERC-20, ERC-721, dan ERC-1155 yang melibatkan alamat yang dipantau; periksa standard, token, token_id, amount, dan batch_index. Transfer batch ERC-1155 menghasilkan satu peristiwa per item. |
log | Log lain yang menyebutkan alamat yang dipantau sebagai kontrak pemancar atau dalam topik 1–3; periksa address, topics, data, dan matched. |
subscription.gap | Rentang dari from_block hingga to_block tidak tersedia untuk pengiriman, dengan reason: retention_expired; lakukan backfill dengan Data API atau eth_getLogs. |
chain.reorg | Pemberitahuan reorg gratis: blok terkirim di from_block–to_block telah diganti. Tandai atau buang peristiwa lama berdasarkan ref, lalu simpan peristiwa kanonis yang dikirim ulang secara otomatis dan hapus duplikasi berdasarkan id. |
Dalam sebuah langganan, hapus duplikasi berdasarkan id peristiwa; di seluruh langganan gunakan ref dan type. Abaikan bidang dan jenis peristiwa yang tidak dikenal. Verifikasi fakta on-chain sebelum mengambil tindakan finansial.
Verifikasi tanda tangan
Headernya adalah webhook-id, webhook-timestamp, webhook-signature, dan bv-subscription-id. Pilih rahasia hanya dari langganan yang Anda buat; tolak ID yang tidak dikenal. Verifikasi HMAC-SHA256 melalui webhook-id.webhook-timestamp.raw-body, menggunakan byte dari body permintaan asli, sebelum mem-parsing JSON. Tanda tangannya adalah v1,<base64>; izinkan perbedaan waktu (skew) timestamp sekitar lima menit dan bandingkan dalam waktu konstan.
Fungsi Node.js ini menerima body mentah sebagai Buffer, header permintaan, dan peta ID langganan ke rahasia yang disimpan:
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verifyPush(rawBody, headers, secrets) {
const subscriptionId = headers['bv-subscription-id'];
const id = headers['webhook-id'];
const timestamp = headers['webhook-timestamp'];
const signature = headers['webhook-signature'];
if ([subscriptionId, id, timestamp, signature].some(v => typeof v !== 'string')) return false;
const secret = secrets.get(subscriptionId);
if (typeof secret !== 'string' || !secret.startsWith('whsec_')) return false;
if (!/^\d{10}$/.test(timestamp) || Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
const match = /^v1,([A-Za-z0-9+/]{43}=)$/.exec(signature);
if (!match) return false;
const received = Buffer.from(match[1], 'base64');
const expected = createHmac('sha256', Buffer.from(secret.slice(6), 'base64'))
.update(`${id}.${timestamp}.`).update(rawBody).digest();
return received.length === expected.length && timingSafeEqual(received, expected);
}Setelah verifikasi, parse body, simpan pemrosesan secara persisten, dan kembalikan 2xx dalam waktu 10 detik. Header ID langganan tidak tepercaya sampai tanda tangan diverifikasi.
Verifikasi peristiwa pertama Anda
Biarkan langganan tetap online. Setelah perubahan alamat diterapkan, tunggu aktivitas on-chain yang cocok dan periksa bahwa penerima Anda memverifikasi dan menyimpan peristiwa tersebut secara persisten.
Hentikan mendengarkan setelah verifikasi
Untuk berhenti memantau alamat, simpan alamat yang akan dihapus di addresses.json dan panggil POST /subscriptions/{subscription_id}/addresses/remove:
curl --fail-with-body -sS "$PUSH_URL/subscriptions/$SUBSCRIPTION_ID/addresses/remove" \
-H "x-api-key: $BLOCKVECTRA_API_KEY" \
-H 'Content-Type: application/json' -d @addresses.jsonSetiap panggilan penghapusan menerima paling banyak 10.000 alamat. Alamat yang saat ini tidak dipantau dihitung sebagai unchanged. Panggilan tersebut mengembalikan change_version. Setelah applied_version >= change_version, blok dari blok efektif tersebut dan seterusnya tidak lagi cocok dengan alamat yang dihapus. Peristiwa yang cocok sebelumnya (sedang dikirim, mencoba ulang, atau dalam antrean) tetap dikirimkan; peristiwa yang sudah dikirimkan tidak ditarik kembali.
Untuk menjeda mendengarkan sementara tanpa menghapus konfigurasi atau alamat, tetapkan status ke offline:
curl --fail-with-body -sS -X PATCH "$PUSH_URL/subscriptions/$SUBSCRIPTION_ID" \
-H "x-api-key: $BLOCKVECTRA_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"status":"offline"}'Langganan offline menghentikan mendengarkan dan pengiriman, mengeluarkan alamat dari indeks pencocokan, dan tidak dikenakan biaya alamat untuk setiap hari UTC penuh saat statusnya tetap offline. Semua konfigurasi (URL, rahasia, alamat, rantai, dan konfirmasi) tetap dipertahankan. Memperbarui dengan {"status":"online"} melanjutkan mendengarkan dari blok efektif saat ini dan tidak melakukan backfill untuk periode offline.
Gunakan JSON Merge Patch pada PATCH /subscriptions/{subscription_id} untuk mengubah url, key_id, status, atau chains: objek rantai menambah atau memperbaruinya, dan null menghapusnya. Setidaknya satu rantai harus tetap ada. Untuk menghapus langganan secara permanen:
curl --fail-with-body -sS -X DELETE "$PUSH_URL/subscriptions/$SUBSCRIPTION_ID" \
-H "x-api-key: $BLOCKVECTRA_API_KEY"DELETE menghapus langganan secara permanen, menghentikan pengiriman di semua rantai dengan segera, dan memusnahkan rahasia serta alamat.
Pengiriman, percobaan ulang, dan pemutaran ulang
Pengiriman dilakukan setidaknya sekali. Rantai dari setiap langganan diurutkan berdasarkan blok dan posisi dalam blok; batch yang gagal memblokir peristiwa selanjutnya pada rantai tersebut. Rantai yang berbeda memiliki kemajuan independen dan dapat melakukan POST secara bersamaan. Percobaan ulang dari batch yang identik mempertahankan webhook-id, tetapi batch yang berubah dapat memiliki ID baru: deduplikasi peristiwa, bukan batch.
Respons 2xx apa pun dalam waktu 10 detik menandakan konfirmasi pemrosesan persisten. Pengalihan (redirect) tidak diikuti; 3xx dan 410 dianggap kegagalan. Setelah kegagalan, percobaan ulang mengikuti interval segera, 5 detik, 30 detik, 2 menit, 10 menit, 30 menit, dan 1 jam, lalu setiap jam. Retry-After 429 dapat memperpanjang waktu tunggu hingga satu jam. Periksa condition, last_error, dan next_attempt_at setiap rantai saat pengiriman berhenti. Kondisinya adalah receiver_failing, insufficient_balance, dan key_revoked; yang terakhir mengharuskan pembaruan key_id ke kunci akun aktif lainnya.
Peristiwa yang tidak terkirim kedaluwarsa di luar jendela retensi dan menghasilkan subscription.gap. POST /subscriptions/{subscription_id}/replay menerima chain dan from_block; periksa replayable_from_block di GET /push/chains dan kemajuan langganan. Replay mengirimkan kecocokan yang ada dan tidak dapat memulihkan peristiwa dari sebelum alamat atau rantai ditambahkan.
chain.reorg memberi tahu Anda bahwa blok yang sudah terkirim telah diganti; ini tidak menunjukkan celah pengiriman. Reorg yang lebih dangkal dari jumlah konfirmasi Anda tidak akan terlihat. Untuk reorg yang memengaruhi blok terkirim hingga kedalaman 1.024 blok, peristiwa kanonis dikirim ulang secara otomatis dengan id baru. Tandai atau buang peristiwa yang diganti berdasarkan ref, simpan peristiwa kanonis, dan hapus duplikasi berdasarkan id; untuk catatan pembayaran, rekonsiliasi berdasarkan ref dan tx_hash. Reorg yang lebih dalam menghentikan rantai: periksa halted di GET /push/chains; pengiriman ulang kanonis menyusul setelah rantai dipulihkan. Peristiwa kontrol tidak memajukan complete_through_block.
Kueri peristiwa data yang dikirim dengan GET /subscriptions/{subscription_id}/events?chain=..., secara opsional menambahkan from_block, to_block, limit, dan page_token. Baris riwayat berisi event, replay_epoch, orphaned, dan delivered_at; orphaned: true menandai blok yang kemudian diganti. Penerimaan riwayat dapat mengembalikan 402 insufficient_balance (data.reason: balance_exhausted atau free_grant_exhausted), 403 key_cap_exhausted (data.cu_cap), atau 429 rate_limited (key_rate_limit atau free_plan_call_limit). 429 cost_exceeds_burst memiliki alasan request_exceeds_burst dan data.max: tingkatkan kapasitas burst sebelum mencoba lagi. Lihat penanganan error untuk rentang yang tidak valid dan panduan percobaan ulang.
Penagihan dan contoh
Bobot berasal dari GET /v1/plans. Peristiwa data terkirim, permintaan riwayat yang berhasil, dan hari-alamat memiliki bobot terpisah; panggilan manajemen selain riwayat, peristiwa kontrol, pengiriman gagal, dan percobaan ulang otomatis gratis. Setiap peristiwa yang dikirim dikenakan biaya satu kali; replay pelanggan dan pengiriman ulang peristiwa kanonis dikenakan biaya pengiriman baru.
Penagihan alamat menggunakan jumlah alamat terbesar dari setiap langganan selama bagian online-nya pada hari UTC, setelah kuota alamat gratis akun yang dibagikan ke seluruh langganan (langganan yang lebih lama terlebih dahulu). Alamat yang sama dalam dua langganan dihitung dua kali; menambahkan rantai mengubah biaya peristiwa, bukan biaya alamat. Langganan yang offline sepanjang hari UTC tidak dikenakan biaya alamat.
| Penggunaan | Unit penagihan | CU |
|---|---|---|
push.address_day | Hari-alamat tertagih | 33 |
push.history | Permintaan riwayat berhasil | 25 |
push.log | Peristiwa data terkirim | 150 |
push.native_transfer | Peristiwa data terkirim | 150 |
push.token_transfer | Peristiwa data terkirim | 150 |
Alamat gratis per akun per hari UTC: 1000
Alokasi alamat gratis per akun per hari UTC, dibagikan oleh semua grup langganan terlepas dari paket. Untuk setiap grup, hitung jumlah alamat maksimum saat online selama hari tersebut; alokasikan kuota dalam urutan ID grup menaik. Alamat yang sama dalam dua grup dihitung dua kali; jumlah rantai dalam grup tidak melipatgandakan jumlah alamatnya. Grup yang offline atau dihapus sepanjang hari tidak menyumbang apa pun. Untuk setiap grup, jumlah yang tersisa setelah bagian kuotanya dikalikan dengan bobot CU `push.address_day` di `method_weights`. Kuota terkonfigurasi saat ini berasal dari kebijakan harga yang sama yang digunakan untuk biaya hari-alamat; ini bukan batas kapasitas akun atau kuota terpisah per grup.
Contoh: 10 peristiwa native.transfer terkirim, 2 permintaan riwayat berhasil, dan 10 hari-alamat tertagih berbiaya 10 × 150 + 2 × 25 + 10 × 33 = 1880 CU. Hari-alamat tertagih dihitung setelah kuota alamat gratis akun.
Lihat aturan penagihan dan halaman harga untuk pengukuran dan konversi CU.
Sumber daya terkait
- Bandingkan peristiwa yang didukung, cakupan rantai, dan harga dalam Ikhtisar API Webhook Blockchain.
Terakhir diperbarui: