> For the complete documentation index, see [llms.txt](https://docs.w-chain.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.w-chain.com/advanced-features/b2b-address-notification-service.md).

# 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 normalized
  * `watch_coin` (boolean, optional, default `true`): Watch native coin (WCO) transfers
  * `watch_token` (boolean, optional, default `true`): Watch ERC-20 token transfers
  * `webhook_url` (string, required): HTTPS URL to receive webhooks

Example:

```bash
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):

```json
{
  "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:

```bash
curl -X GET https://oracle.w-chain.com/api/notifications/watched-addresses \
  -H "Authorization: Bearer YOUR_API_KEY"
```

Success response (200):

```json
{
  "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:

```bash
curl -X GET https://oracle.w-chain.com/api/notifications/watched-addresses/ID_HERE \
  -H "Authorization: Bearer YOUR_API_KEY"
```

Success response (200):

```json
{
  "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:

```bash
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):

```json
{
  "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:

```bash
curl -X DELETE https://oracle.w-chain.com/api/notifications/watched-addresses/ID_HERE \
  -H "Authorization: Bearer YOUR_API_KEY"
```

Success response (200):

```json
{ "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/json`
  * `Authorization: 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 ID
* `watched_addresses_id` (string, UUID): The watched\_addresses row ID
* `watcher_id` (number): Your watcher ID
* `hash` (string): 0x-prefixed transaction hash
* `type` (string enum): `COIN_TRANSFER` or `TOKEN_TRANSFER`
* `sender` (string): 0x-prefixed sender address
* `receiver` (string): 0x-prefixed receiver address. For contract creations or transactions without a `to`, 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
* `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:

```json
{
  "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_count` increments on each failure.
* `next_retry_at` is set to `now + interval`.
* On a successful 2xx response, `processed_at` is 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) `asset` with 0x.
* Receiver may be empty string
  * For coin transfers where `to` is null (e.g., contract creation), receiver is emitted as an empty string.
* 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
  * `WCO` for 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>`.

### Example Receiver (Pseudocode)

```ts
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_KEY` is present and valid.
* 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 NULL` and `next_retry_at` in the past.
* Amount precision issues
  * The `amount` is sent as a JSON number; for exact arithmetic, convert or handle using big number libraries on your side.

### Change Log

* 2025-09: Added `asset` to notification schema and payload. For coin transfers set to `WCO`; 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).
