Introduction
What this platform does
Contract Webhook API is a managed infrastructure service that connects your applications to on-chain smart contract events via reliable HTTP webhooks. You register a contract address and ABI, specify which events to listen for, and we deliver decoded event payloads to your webhook endpoint — with persistence, retries, and guaranteed delivery.
The problem space
Why on-chain event consumption is fragile
Listening to blockchain events sounds simple until it isn't. RPC providers drop WebSocket connections without warning. Nodes fall behind, return stale data, or rate-limit you at the worst possible moment. Your listener crashes at 3am and misses a week of transfers before anyone notices.
Why listeners, RPCs, and retries fail in practice
Most teams start with ethers.js or web3.js event listeners. They work great in development.
Then production happens: the RPC endpoint goes down, the listener process gets OOM-killed, or the database transaction
fails mid-processing. Suddenly you're writing retry logic, checkpoint persistence, reconnection handlers, and dead letter
queues. You've built half an event-sourcing system when you just wanted to track token transfers.
Why this keeps showing up across teams
Every team building on-chain integrations eventually hits the same wall. Some build custom indexers. Some pay for expensive indexing services that do far more than they need. Some accept data loss as a cost of doing business. None of these are good options for teams that just need reliable event delivery without the infrastructure overhead.
Design philosophy
- Events are persisted before delivery — We capture and store events before attempting delivery. If your endpoint is down, events queue up safely.
- Failures are expected, not exceptional — Every delivery attempt is tracked. Retries are automatic. No silent failures.
- Delivery is at-least-once — You may receive the same event more than once. Design your handlers to be idempotent.
- Correctness over optimism — We wait for block confirmations before delivery. We don't guess. We verify.
What this platform does NOT do
- No transactions — We don't send transactions on your behalf
- No wallets — We don't manage or store private keys
- No signing — We don't sign anything with your credentials
- No general-purpose indexing — We deliver events, not historical blockchain state
After this section, no more philosophy. Just mechanics.
Quick Start
The fastest possible success path. If you only read this section and nothing else, you should still succeed.
Step 1: Create an API key
Where: Dashboard → API Keys → Create New Key
Header format:
Authorization: Bearer YOUR_API_KEYStep 2: Create a subscription with webhook
{
"chainId": 1,
"contractAddress": "0x1234...abcd",
"abi": [...],
"eventFilters": ["Transfer", "Approval"], // Optional
"webhookUrl": "https://yourapp.com/webhook",
"enableSignature": true // Optional, enabled by default
}- chainId — The blockchain network ID (1 = Ethereum mainnet)
- contractAddress — The smart contract to monitor
- abi — Contract ABI (full or event-only)
- eventFilters — Optional. Specific event names to capture
- webhookUrl — Your HTTPS endpoint that will receive events
The response will contain your subscription ID and a signing secret (if enabled). Save this secret! It won't be shown again.
Step 3: Receive events
Your webhook will receive POST requests with this payload:
{
"id": "evt_xyz789",
"subscriptionId": "sub_abc123",
"chainId": 1,
"blockNumber": 19234567,
"transactionHash": "0xabc...def",
"eventName": "Transfer",
"args": {
"from": "0x1111...1111",
"to": "0x2222...2222",
"value": "1000000000000000000"
},
"timestamp": "2026-01-28T08:30:00Z"
}Respond with a 2xx status code to acknowledge receipt.
Authentication
API Keys
Creating keys
Navigate to Dashboard → API Keys → Create New Key. Keys are shown once at creation. Store them securely.
Using keys
curl -X GET https://api.contractwebhook.io/api/subscriptions \
-H "Authorization: Bearer sk_live_abc123..."Rotating keys
Create a new key before revoking the old one. Update your application, verify it works, then revoke the old key from the dashboard.
Subscriptions
Creating a subscription
Required fields
| Field | Type | Description |
|---|---|---|
chainId | number | Network ID (1=Ethereum, 137=Polygon, etc.) |
contractAddress | string | Contract address to monitor |
abi | array | Contract ABI for event decoding |
webhookUrl | string | HTTPS endpoint to receive events |
Optional fields
| Field | Type | Description |
|---|---|---|
eventFilters | string[] | Event names to capture. Empty = all events |
startBlock | number | Block to start listening from |
ABI handling
Supported formats
- Full contract ABI (standard Solidity compiler output)
- Event-only ABI (array of event definitions)
- Human-readable ABI (
"event Transfer(address,address,uint256)")
What happens on decode failure
If an event cannot be decoded with the provided ABI, we deliver the raw log data with "decoded": false. Your handler receives the event but must decode it manually.
Event filtering
By event name
{
"eventFilters": ["Transfer", "Approval"]
}Why filters matter
High-activity contracts can emit thousands of events per hour. Filtering reduces noise, lowers latency, and keeps your webhook queue focused on events you actually care about.
Webhooks
Delivery behavior
At-least-once delivery
Every event is delivered at least once. Network issues, timeouts, or your endpoint returning errors trigger retries. Design handlers to be idempotent using the id field.
No strict ordering guarantees
Events are delivered in approximate order but not strictly sequential. If ordering matters, use blockNumber and logIndex to sort on your end.
Payload format
JSON schema
| Field | Type | Description |
|---|---|---|
id | string | Unique event ID (for deduplication) |
subscriptionId | string | Your subscription reference |
chainId | number | Network the event occurred on |
blockNumber | number | Block containing the event |
transactionHash | string | Transaction that emitted the event |
logIndex | number | Position within the block's logs |
eventName | string | Decoded event name |
args | object | Decoded event arguments |
timestamp | string | ISO 8601 delivery timestamp |
Example payload
{
"id": "evt_01HN4X...",
"subscriptionId": "sub_abc123",
"chainId": 1,
"blockNumber": 19234567,
"transactionHash": "0xabc123...",
"logIndex": 42,
"eventName": "Transfer",
"args": {
"from": "0x1111...1111",
"to": "0x2222...2222",
"value": "1000000000000000000"
},
"timestamp": "2026-01-28T09:30:00.000Z"
}Signing & verification
How to verify
If you provided a signing secret, each request includes an X-Webhook-Signature header:
const crypto = require('crypto');
function verifySignature(payload, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}Why you should
Without verification, anyone who discovers your webhook URL can send fake events. Always verify in production.
Retries
When retries happen
- Your endpoint returns a non-2xx status code
- Connection timeout (30 seconds)
- Network error (DNS failure, connection refused)
When they stop
After 5 attempts with exponential backoff (1min, 5min, 30min, 2hr, 12hr). Failed events are marked as dead and visible in your dashboard for manual replay.
Replays
When to replay
- Webhook downtime — Your endpoint was down and events failed delivery
- Consumer bugs — Your handler had a bug that caused incorrect processing
How to replay
{
"subscriptionId": "sub_abc123",
"fromTimestamp": "2026-01-27T00:00:00Z",
"toTimestamp": "2026-01-28T00:00:00Z"
}- Time range — Specify start and end timestamps for the replay window
- Subscription scope — Replays are scoped to a single subscription
All failed or delivered events within the window will be reset to PENDING status and redelivered.
What replays guarantee
- Same payloads — You receive the exact same event data as the original delivery
- New delivery attempts — Each replayed event counts as a fresh delivery with its own retry policy
Rate Limits
API limits
| Endpoint | Limit |
|---|---|
| All API endpoints | 100 requests/minute per API key |
| Subscription creation | 10 per minute |
| Replay requests | 5 per hour |
Webhook throughput expectations
| Plan | Events/second |
|---|---|
| Free | 10 events/sec |
| Pro | 100 events/sec |
| Enterprise | Custom |
Events exceeding your throughput limit are queued, not dropped. Delivery is delayed but guaranteed.
Errors & Debugging
API errors
Status codes
| Code | Meaning |
|---|---|
400 | Invalid request body or parameters |
401 | Missing or invalid API key |
403 | API key lacks permission for this action |
404 | Resource not found |
429 | Rate limit exceeded |
500 | Internal server error |
Common causes
- 400 on subscription creation — Invalid ABI format or unsupported chain ID
- 401 on all requests — API key not in
Authorization: Bearerformat - 429 suddenly — Tight loop or retry storm. Add exponential backoff.
Delivery failures
What counts as failure
- Non-2xx response from your endpoint
- Request timeout (30 seconds)
- Connection error (DNS, TLS, network)
How to inspect delivery attempts
Dashboard → Subscriptions → [Your Subscription] → Delivery Logs
Each attempt shows: timestamp, HTTP status, response body (first 1KB), and retry count.
Security Notes
No private keys
We never ask for, store, or have access to your private keys. This service is read-only. We listen to public blockchain events and deliver them to you.
Signed webhooks
Always configure a webhook secret and verify signatures. This prevents attackers from injecting fake events into your system.
Recommended practices
- Use HTTPS endpoints only (we reject HTTP URLs)
- Rotate API keys periodically
- Use separate API keys for different environments (dev, staging, prod)
- Validate event data before processing (check contract address, chain ID)
- Implement idempotency using event IDs
FAQ
Can I receive events from multiple contracts in one subscription?
No. One subscription = one contract. Create multiple subscriptions for multiple contracts.
How fast are events delivered after they're emitted on-chain?
Typically under 500ms after block confirmation. We wait for 2-12 confirmations depending on the chain to avoid delivering events from reorged blocks.
What happens if my webhook is down for a week?
Events queue up. When your endpoint comes back, we deliver them in order with standard retry logic. Use replays for bulk re-processing if needed.
Do you support testnets?
Yes. Goerli, Sepolia, Mumbai, and other major testnets are supported. Same API, same reliability.
Can I filter by specific argument values (e.g., transfers to my address)?
Not yet. Current filtering is by event name only. Topic-based filtering is on the roadmap.
What's the maximum payload size?
Events with decoded arguments exceeding 1MB are truncated. Raw log data is always included for large events.
How do I handle duplicate events?
Use the id field. Store processed event IDs and skip duplicates. This is required for at-least-once delivery semantics.