B2B Address Notification Service
Coin & Token Transfer Watcher
This document explains how to use the Watched Addresses system to receive real-time webhook notifications for W Chain coin (WCO) transfers and ERC-20 token transfers involving your registered addresses.
The system provides:
- A CRUD API to manage watched addresses and webhook URLs
- Real-time POST webhooks on transfers matching your watch rules
- Automatic retries with exponential backoff on delivery failures
All user-facing addresses and hashes in API responses and webhooks are 0x-prefixed. Internally, addresses are stored lowercase without the 0x prefix.
Authentication
All endpoints require an API key via the HTTP Authorization header:
- Authorization: Bearer YOUR_API_KEY
If the key is valid, the server resolves your watcher_id and authorizes the request. If invalid or missing, the server returns 401 Unauthorized.
Base Path
- Base URL: https://oracle.w-chain.com/api
Address Normalization
- Input (create/update): You may send addresses with or without the 0x prefix. The server normalizes by stripping 0x if present and storing lowercase hex without 0x.
- Output (list/get/update responses and webhooks): All addresses and transaction hashes are 0x-prefixed for consistency with wallets and explorers.
Endpoints
Create a watched address
- Method:
POST - Path:
/notifications/watched-addresses - Body fields:
address(string, required): Address with or without 0x; must be 40 hex characters when normalizedwatch_coin(boolean, optional, defaulttrue): Watch native coin (WCO) transferswatch_token(boolean, optional, defaulttrue): Watch ERC-20 token transferswebhook_url(string, required): HTTPS URL to receive webhooks
Example:
curl -X POST https://oracle.w-chain.com/api/notifications/watched-addresses \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"address": "0xabc1234567890abcdefabc1234567890abcdef0",
"watch_coin": true,
"watch_token": true,
"webhook_url": "https://yourapp.example.com/watched-address-webhook"
}'Success response (200):
{
"success": true,
"data": {
"id": "...",
"watcher_id": 123,
"address": "0xabc1234567890abcdefabc1234567890abcdef0",
"watch_coin": true,
"watch_token": true,
"webhook_url": "https://yourapp.example.com/watched-address-webhook",
"created_at": "...",
"updated_at": "..."
}
}List watched addresses
- Method:
GET - Path:
/notifications/watched-addresses
Example:
curl -X GET https://oracle.w-chain.com/api/notifications/watched-addresses \
-H "Authorization: Bearer YOUR_API_KEY"Success response (200):
{
"success": true,
"data": [
{
"id": "...",
"watcher_id": 123,
"address": "0xabc1234567890abcdefabc1234567890abcdef0",
"watch_coin": true,
"watch_token": true,
"webhook_url": "https://yourapp.example.com/watched-address-webhook",
"created_at": "...",
"updated_at": "..."
}
]
}Get a watched address by ID
- Method:
GET - Path:
/notifications/watched-addresses/:id
Example:
curl -X GET https://oracle.w-chain.com/api/notifications/watched-addresses/ID_HERE \
-H "Authorization: Bearer YOUR_API_KEY"Success response (200):
{
"success": true,
"data": {
"id": "...",
"watcher_id": 123,
"address": "0xabc1234567890abcdefabc1234567890abcdef0",
"watch_coin": true,
"watch_token": true,
"webhook_url": "https://yourapp.example.com/watched-address-webhook",
"created_at": "...",
"updated_at": "..."
}
}Update a watched address
- Method:
PATCH - Path:
/notifications/watched-addresses/:id - Body: Any subset of the create fields (
address,watch_coin,watch_token,webhook_url)
Example:
curl -X PATCH https://oracle.w-chain.com/api/notifications/watched-addresses/ID_HERE \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "watch_token": false }'Success response (200):
{
"success": true,
"data": {
"id": "...",
"watcher_id": 123,
"address": "0xabc1234567890abcdefabc1234567890abcdef0",
"watch_coin": true,
"watch_token": false,
"webhook_url": "https://yourapp.example.com/watched-address-webhook",
"created_at": "...",
"updated_at": "..."
}
}Delete a watched address
- Method:
DELETE - Path:
/notifications/watched-addresses/:id
Example:
curl -X DELETE https://oracle.w-chain.com/api/notifications/watched-addresses/ID_HERE \
-H "Authorization: Bearer YOUR_API_KEY"Success response (200):
{ "success": true }Webhook Delivery
When a transfer involving any of your watched addresses occurs, we send a POST request with JSON to the webhook_url configured for that watched address.
- Method:
POST - Headers:
Content-Type: application/jsonAuthorization: Bearer <api_key>this is the same API Key used by the Watcher identity, should be used to ensure the authenticity of the webhook request
- Timeout: 10 seconds. If your endpoint does not respond within 10 seconds, it is treated as a failure and retried.
Payload fields
These fields are sent in the request body. Values are formatted for user-facing consumption as described.
id(number): Internal notification IDwatched_addresses_id(string, UUID): The watched_addresses row IDwatcher_id(number): Your watcher IDhash(string): 0x-prefixed transaction hashtype(string enum):COIN_TRANSFERorTOKEN_TRANSFERsender(string): 0x-prefixed sender addressreceiver(string): 0x-prefixed receiver address. For contract creations or transactions without ato, this may be an empty string.amount(number | null):- COIN_TRANSFER: Amount is in wei, as a JSON number. Note: very large values may exceed JavaScript’s exact integer range; handle carefully if you require exact arithmetic.
- TOKEN_TRANSFER: Raw token units as a JSON number (decimals not applied). Fetch the token’s decimals and normalize on your side if needed.
asset(string):- COIN_TRANSFER: The literal string
WCO - TOKEN_TRANSFER: 0x-prefixed token contract address
- COIN_TRANSFER: The literal string
timestamp(string): Timestamp of notification row creation as produced by Postgres (timestamp without time zone). Treat as a textual timestamp; convert/timezone as desired on your side.webhook_url(string): The target URL (provided for your reference)api_key(string | null): The API key stored for your watcher, if any (also sent via the Authorization header)next_retry_at(string | null): Scheduled time for the next retry (if previously failed), else null
Example payload:
{
"id": 12345,
"watched_addresses_id": "a1b2c3d4-1111-2222-3333-abcdefabcdef",
"watcher_id": 678,
"hash": "0x3fa9e1...c1d2",
"type": "TOKEN_TRANSFER",
"sender": "0x1111111111111111111111111111111111111111",
"receiver": "0x2222222222222222222222222222222222222222",
"amount": 2500000000000000000,
"asset": "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
"timestamp": "2025-09-10 08:00:00.000000",
"webhook_url": "https://yourapp.example.com/watched-address-webhook",
"api_key": "YOUR_API_KEY_OR_NULL",
"next_retry_at": null
}Retry Policy (Exponential Backoff)
If a webhook delivery fails (non-2xx response, timeout, or network error), the system schedules a retry using the following backoff (minutes):
- 1, 3, 10, 15, 60
On the Nth failure, the Nth interval is used; after the 5th failure, retries continue every 60 minutes.
retry_countincrements on each failure.next_retry_atis set tonow + interval.- On a successful 2xx response,
processed_atis set and the notification will not be retried further.
The retry worker selects unprocessed notifications whose next_retry_at is due.
Implementation Notes
- Address formatting
- Inputs accept with or without 0x; stored lowercase without 0x internally.
- API responses (create/list/get/update) return addresses with 0x.
- Webhook payload prefixes
hash,sender,receiver, and (for tokens)assetwith 0x.
- Receiver may be empty string
- For coin transfers where
tois null (e.g., contract creation), receiver is emitted as an empty string.
- For coin transfers where
- Amount type
- Currently sent as a JSON number. If exact precision is required in your system, convert or handle carefully to avoid precision loss in JavaScript environments.
- Asset field
WCOfor native coin transfers; otherwise the token contract address (0x-prefixed) for token transfers.
- Timeouts
- Webhook POSTs time out in 10 seconds (treated as failure).
- Authorization header on webhook
- If an API key is present for the watcher, the webhook request to your server includes
Authorization: Bearer <api_key>.
- If an API key is present for the watcher, the webhook request to your server includes
Example Receiver (Pseudocode)
import express from 'express';
const app = express();
app.use(express.json());
app.post('/watched-address-webhook', (req, res) => {
// Optionally verify Authorization header matches what you expect
// const auth = req.headers['authorization'];
const n = req.body; // See payload fields above
// Handle idempotency if you store notifications (e.g., by `n.id`)
if (n.type === 'COIN_TRANSFER') {
// n.asset === 'WCO'
// n.amount is wei (number); beware precision in JS
} else if (n.type === 'TOKEN_TRANSFER') {
// n.asset is token contract address (0x-prefixed)
// n.amount is raw units; fetch token decimals to normalize
}
res.status(200).send('ok');
});
app.listen(3000);Troubleshooting
- 401 Unauthorized on API calls
- Ensure
Authorization: Bearer YOUR_API_KEYis present and valid.
- Ensure
- Webhook not received
- Confirm your server is reachable over HTTPS and returns 200 within 10 seconds.
- Check if retries are scheduled by inspecting your logs or querying notifications with
processed_at IS NULLandnext_retry_atin the past.
- Amount precision issues
- The
amountis sent as a JSON number; for exact arithmetic, convert or handle using big number libraries on your side.
- The
Change Log
- 2025-09: Added
assetto notification schema and payload. For coin transfers set toWCO; for token transfers set to the token contract address. - 2025-09: Standardized 0x-prefix formatting in API responses and webhook payloads (hash, sender, receiver, token asset).