> ## Documentation Index
> Fetch the complete documentation index at: https://docs.webacy.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Monitor Vaults, Stablecoins, and RWAs on a Cadence

> Monitor allocated vaults, stablecoins, and RWAs: poll graded ratings, track depeg deviation and operational signals, and alert on threshold breaches.

A vault you have allocated to does not hold still. Between reviews a curator can deallocate, utilization can spike, liquidity can thin out, and a stablecoin you hold can drift off its peg. A one-time allocation check catches none of it. What catches it is a monitor that polls each position on a cadence, compares the numbers against your thresholds, and alerts the moment they move. This guide shows you how to build that monitor for the vaults, stablecoins, and RWAs you have already allocated to.

## Why Continuous Monitoring Matters

<CardGroup cols={3}>
  <Card title="Curator Actions" icon="arrows-rotate">
    Catch deallocations, timelock changes, and curator behavior the moment they happen, not at your next review cycle
  </Card>

  <Card title="Depeg Detection" icon="gauge-high">
    Track peg deviation and liquidity depth on every stablecoin and RWA in your portfolio, continuously
  </Card>

  <Card title="Live Rating in Your UI" icon="chart-line">
    Surface a graded, always-current rating to your users instead of a stale one-time score
  </Card>
</CardGroup>

**Why teams choose Webacy for ongoing monitoring:**

* **Graded rating plus operational detail**: the v3 endpoints give you a letter grade; the v1 vault endpoint gives you curator, utilization, and liquidity so you know *why* the grade moved
* **Depeg-specific signals**: peg deviation, per-DEX liquidity depth, and score deltas built for stablecoins and RWAs
* **Fail-closed by design**: missing or stale data is treated as unknown, never as safe
* **One API for the whole book**: vaults, stablecoins, and RWA vaults from a single base URL

***

## Prerequisites

Before implementing ongoing monitoring, ensure you have:

* A Webacy API key ([sign up here](https://developers.webacy.co/billing))
* Basic familiarity with REST APIs or the [Webacy SDK](/sdk/installation)
* The vaults, stablecoins, and RWA positions you currently hold identified, with their chain and contract address

***

## Monitor Vaults on a Cadence

Two calls, two jobs. Call `/v3/vaults` for the graded letter rating you show to users. Call `/vaults` (v1) for the operational detail that explains *why*, curator, utilization, liquidity, TVL flight, and an exit recommendation.

<Warning>
  **Score polarity: higher means worse.** `composite.score` and every `categories.*.score` on the v3 response run 0-100, where **0 is the lowest risk and 100 is the highest**: `A+` maps to a score near 0, `F` maps to a score near 100. If you sort or color a dashboard by this score, higher must render as *worse*, not better.
</Warning>

<CodeGroup>
  ```bash cURL theme={null}
  # Graded rating (v3)
  curl -X GET "https://api.webacy.com/v3/vaults/0x0deFfd509197aAD5207d2A55862835b467E8128F?chain=eth" \
    -H "x-api-key: YOUR_API_KEY"

  # Operational detail (v1)
  curl -X GET "https://api.webacy.com/vaults/0x0deFfd509197aAD5207d2A55862835b467E8128F?chain=eth" \
    -H "x-api-key: YOUR_API_KEY"
  ```

  ```typescript TypeScript theme={null}
  const headers = { 'x-api-key': process.env.WEBACY_API_KEY! };

  const [gradedRes, opsRes] = await Promise.all([
    fetch('https://api.webacy.com/v3/vaults/0x0deFfd509197aAD5207d2A55862835b467E8128F?chain=eth', { headers }),
    fetch('https://api.webacy.com/vaults/0x0deFfd509197aAD5207d2A55862835b467E8128F?chain=eth', { headers }),
  ]);

  const graded = await gradedRes.json();
  const ops = await opsRes.json();

  console.log(`Grade: ${graded.composite.grade} (score ${graded.composite.score}/100, higher = worse)`);
  console.log(`Curator: ${ops.morpho?.curator}`);
  console.log(`Utilization: ${ops.metadata.utilization_rate}`);
  console.log(`Exit recommended: ${ops.exit_recommendation}`);
  ```

  ```python Python theme={null}
  import requests

  headers = {"x-api-key": "YOUR_API_KEY"}
  base = "https://api.webacy.com"
  address, chain = "0x0deFfd509197aAD5207d2A55862835b467E8128F", "eth"

  graded = requests.get(f"{base}/v3/vaults/{address}", params={"chain": chain}, headers=headers).json()
  ops = requests.get(f"{base}/vaults/{address}", params={"chain": chain}, headers=headers).json()

  print(graded["composite"]["grade"], graded["composite"]["score"])
  print(ops["morpho"]["curator"], ops["metadata"]["utilization_rate"])
  ```
</CodeGroup>

**Which field answers which question:**

| Question                             | Field                                                                                      | Source         |
| ------------------------------------ | ------------------------------------------------------------------------------------------ | -------------- |
| What's the current letter grade?     | `composite.grade`, `composite.stars`                                                       | `/v3/vaults`   |
| How risky, numerically?              | `composite.score` (0-100, higher = worse)                                                  | `/v3/vaults`   |
| Which category is driving the grade? | `categories.*.score`, `categories.*.criteria`                                              | `/v3/vaults`   |
| How complete is the scoring?         | `coverage.total_criteria`, `coverage.live_criteria`, `coverage.framework_version`          | `/v3/vaults`   |
| Who curates this vault?              | `morpho.curator`                                                                           | `/vaults` (v1) |
| How much can I withdraw right now?   | `morpho.liquidity_usd`                                                                     | `/vaults` (v1) |
| Is the vault over-utilized?          | `metadata.utilization_rate`                                                                | `/vaults` (v1) |
| Is capital fleeing the vault?        | `metadata.tvl_flight_1w`, `metadata.tvl_usd`                                               | `/vaults` (v1) |
| Would Webacy list this vault today?  | `metadata.listing_verdict`                                                                 | `/vaults` (v1) |
| Should I exit this position?         | `exit_recommendation`, `attention_needed`                                                  | `/vaults` (v1) |
| Is the vault exposed to LSTs?        | `metadata.lst_collateral_pct`, `metadata.lst_collateral_symbols`, `lst_collateral_markets` | `/vaults` (v1) |
| Is this response current?            | `stale`                                                                                    | `/vaults` (v1) |

***

## Monitor Stablecoins and RWAs for Depeg

Every stablecoin and RWA in your book can drift off its peg between allocation checks. Poll `/rwa/{address}` for peg deviation, per-DEX liquidity depth, and short-term score deltas.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://api.webacy.com/rwa/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48?chain=eth&hours=24" \
    -H "x-api-key: YOUR_API_KEY"
  ```

  ```typescript TypeScript theme={null}
  const res = await fetch(
    'https://api.webacy.com/rwa/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48?chain=eth&hours=24',
    { headers: { 'x-api-key': process.env.WEBACY_API_KEY } }
  );
  const detail = await res.json();

  console.log(`Price: $${detail.snapshot.price}, peg: $${detail.snapshot.peg_value}`);
  console.log(`Deviation: ${detail.snapshot.dev_clean} (abs: ${detail.snapshot.abs_dev_clean})`);
  console.log(`Tier: ${detail.snapshot.tier}, score: ${detail.snapshot.score}`);
  console.log(`24h score delta: ${detail.token.score_delta_24h}`);

  for (const market of detail.token.markets ?? []) {
    console.log(`${market.dex} ${market.pair}: $${market.liquidity_usd} liquidity`);
  }
  ```

  ```python Python theme={null}
  response = requests.get(
      "https://api.webacy.com/rwa/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
      params={"chain": "eth", "hours": 24},
      headers={"x-api-key": "YOUR_API_KEY"}
  )
  detail = response.json()
  print(detail["snapshot"]["dev_clean"], detail["snapshot"]["tier"])
  ```
</CodeGroup>

**Key response fields:**

| Field                                            | Description                                         | Action                                       |
| ------------------------------------------------ | --------------------------------------------------- | -------------------------------------------- |
| `snapshot.price` / `snapshot.peg_value`          | Current price vs. expected peg                      | Compute live deviation                       |
| `snapshot.dev_clean` / `snapshot.abs_dev_clean`  | Cleaned peg deviation                               | Alert when it exceeds your threshold         |
| `snapshot.tier`                                  | `ok` / `watch` / `warning` / `critical` / `premium` | Quick categorization                         |
| `snapshot.score`                                 | Depeg risk score                                    | Track trend over your poll history           |
| `token.markets[].liquidity_usd`                  | Liquidity depth per DEX pool                        | Confirm you can actually exit at size        |
| `token.score_delta_24h` / `token.score_delta_7d` | Short-term score movement                           | Catch acceleration before it hits `critical` |

<Tip>
  **Try it now**: Call `/rwa/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48?chain=eth` for USDC. In steady state `dev_clean` sits near zero and `tier` reads `ok`. Use this as your baseline before wiring up alerts on assets you actually hold.
</Tip>

***

## Monitor RWA Vaults

If a position is an RWA-backed vault rather than a plain token, rate it with the RWA graded endpoint, same composite shape as vault v3, same polarity.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://api.webacy.com/v3/rwa/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48?chain=eth" \
    -H "x-api-key: YOUR_API_KEY"
  ```

  ```typescript TypeScript theme={null}
  const res = await fetch(
    'https://api.webacy.com/v3/rwa/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48?chain=eth',
    { headers: { 'x-api-key': process.env.WEBACY_API_KEY } }
  );
  const rwa = await res.json();

  console.log(`Grade: ${rwa.composite.grade} (${rwa.composite.stars} stars)`);
  console.log(`Score: ${rwa.composite.score}/100 (higher = worse)`);
  console.log(`Coverage: ${rwa.coverage.live_criteria}/${rwa.coverage.total_criteria} criteria live`);
  ```

  ```python Python theme={null}
  response = requests.get(
      "https://api.webacy.com/v3/rwa/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
      params={"chain": "eth"},
      headers={"x-api-key": "YOUR_API_KEY"}
  )
  rwa = response.json()
  print(rwa["composite"]["grade"], rwa["composite"]["score"])
  ```
</CodeGroup>

**Key response fields:**

| Field                                                                             | Description                              |
| --------------------------------------------------------------------------------- | ---------------------------------------- |
| `composite.grade` / `composite.stars`                                             | Letter grade and star rating             |
| `composite.score`                                                                 | 0-100, higher = worse                    |
| `categories.*.score` / `categories.*.criteria`                                    | Per-category breakdown driving the grade |
| `coverage.framework_version`, `coverage.total_criteria`, `coverage.live_criteria` | How complete the scoring is              |

`chain` is required on both `/v3/vaults/{address}` and `/v3/rwa/{address}`, always pass it explicitly.

***

## Set Up Alerting

Alert on state changes, not just raw levels, a vault sliding from `B+` to `C` matters more than a vault that's been `C` for a month.

**Recommended thresholds:**

| Signal                 | Trigger                                                                                          | Source                  |
| ---------------------- | ------------------------------------------------------------------------------------------------ | ----------------------- |
| Grade band crossed     | `composite.grade` moves into a worse band (e.g. `B` → `C` or worse)                              | `/v3/vaults`, `/v3/rwa` |
| Depeg tier crossed     | `snapshot.tier` moves to `watch`, `warning`, or `critical`                                       | `/rwa/{address}`        |
| Score accelerating     | `token.score_delta_24h` or `token.score_delta_7d` exceeds your limit                             | `/rwa/{address}`        |
| Liquidity drying up    | `tvl_flight_1w` exceeds your outflow limit, or `morpho.liquidity_usd` drops below your exit size | `/vaults` (v1)          |
| Curator or exit signal | `exit_recommendation` or `attention_needed` flips `true`                                         | `/vaults` (v1)          |

**Poll cadence**: every 5-15 minutes for depeg-sensitive positions, hourly for vault and RWA grades, which don't move as fast as peg deviation. Never poll less often than once a day for anything you'd need to exit in a hurry.

**Fail-closed**: a missing endpoint, a `4xx`/`5xx`, or a `stale: true` response is not a clean bill of health. Treat it as unknown risk and alert accordingly. See [Hard-gating and fail behavior](/api-reference/introduction#hard-gating-and-fail-behavior).

<Warning>
  **Freshness caveat**: the v3 detail endpoints (`/v3/vaults/{address}`, `/v3/rwa/{address}`) do not return a top-level `stale` flag or `generated_at`. Use `metadata.last_scored_at` on the response, or fall back to a grades-list endpoint for a `stale` flag. The v1 `/vaults/{address}` and `/rwa/{address}` endpoints do carry `stale` directly, so check it on every poll.
</Warning>

<Info>
  Prefer push over polling where you can. For stablecoin and RWA depeg you can subscribe to `DEPEG_TIER_CHANGE` webhooks and get notified the moment a tier changes, instead of polling on a timer. See [Webhooks](/essentials/webhooks) to set them up, then keep polling as a backstop for the vault and RWA grades.
</Info>

```typescript theme={null}
interface AlertConfig {
  scoreDeltaLimit: number;   // e.g. 15
  tvlFlightLimit: number;    // e.g. -0.10 (10% outflow in 1w)
  worseGrades: string[];     // grades that should trigger an alert, e.g. ["C", "C-", "D", "F"]
}

function checkAlerts(
  graded: { composite: { grade: string }; metadata: { last_scored_at: string } },
  ops: { metadata: { tvl_flight_1w: number }; exit_recommendation: boolean; attention_needed: boolean; stale: boolean },
  depeg: { snapshot: { tier: string }; token: { score_delta_24h: number }; stale: boolean },
  config: AlertConfig
) {
  const alerts: string[] = [];

  // Fail-closed: stale or missing data is never treated as safe
  if (ops.stale || depeg.stale) {
    alerts.push('DATA STALE, treat position as unknown risk');
    return alerts;
  }

  if (config.worseGrades.includes(graded.composite.grade)) {
    alerts.push(`Grade degraded to ${graded.composite.grade}`);
  }
  if (['watch', 'warning', 'critical'].includes(depeg.snapshot.tier)) {
    alerts.push(`Depeg tier: ${depeg.snapshot.tier}`);
  }
  if (Math.abs(depeg.token.score_delta_24h) >= config.scoreDeltaLimit) {
    alerts.push(`24h score delta: ${depeg.token.score_delta_24h}`);
  }
  if (ops.metadata.tvl_flight_1w <= config.tvlFlightLimit) {
    alerts.push(`TVL flight (1w): ${ops.metadata.tvl_flight_1w}`);
  }
  if (ops.exit_recommendation || ops.attention_needed) {
    alerts.push('Exit recommendation or attention flag raised');
  }

  return alerts;
}
```

***

## Implement a Live Rating in Your UI

Surface the graded rating you're polling directly to your users, call `/v3/vaults/{address}` or `/v3/rwa/{address}`, read `composite.grade`, `composite.stars`, and `categories.*`, and render a badge. No need to build your own scoring model.

For the full walkthrough, pulling the fields into a view model, rendering the badge (React and HTML), showing the category breakdown, and handling freshness and failures, see the dedicated guide:

<Card title="Implement a Risk Rating in Your UI" icon="star" href="/guides/implement-a-rating">
  Call the v3 endpoint, pull the grade, stars, score, and category breakdown, and render a badge, with React and HTML examples
</Card>

***

## Monitor Risk-Adjusted Yield

Yield is part of monitoring, not separate from it. For vaults, the v1 detail endpoint returns the net yield directly: read `morpho.avg_net_apy` (and `avg_net_apy_ex_rewards`, which strips reward incentives) alongside the risk signals you are already polling, so a falling grade next to a climbing APY is easy to spot.

A high APY on its own is not a good position. An APY that spikes well above its recent average is usually a stress signal (an exploit, an oracle failure, or a liquidation cascade), not an opportunity. Webacy's Yield Scores capture exactly this: raw APY discounted by the issuer's safety score, with spike detection that falls back to the 30-day mean when a pool's APY jumps. Rank yield-bearing positions by risk-adjusted APY rather than raw APY.

For the full methodology, see the [Yield Scores](/yield-scores) page.

***

## Complete Monitoring Workflow

```mermaid theme={null}
flowchart TD
    A[Scheduled poll] --> B[Fetch vault v3 + v1 for each vault]
    A --> C[Fetch /rwa depeg detail for each stablecoin/RWA]
    A --> D[Fetch /v3/rwa for each RWA vault]
    B --> E{Compare vs thresholds}
    C --> E
    D --> E
    E -->|Threshold breached| F[Emit alert]
    E -->|Grade/tier changed| G[Update UI badge]
    E -->|No change| H[No-op, log and continue]
```

<Accordion title="Full TypeScript Implementation">
  ```typescript theme={null}
  const API_BASE = 'https://api.webacy.com';
  const headers = { 'x-api-key': process.env.WEBACY_API_KEY! };

  interface Position {
    type: 'vault' | 'rwa_token' | 'rwa_vault';
    address: string;
    chain: string;
  }

  async function fetchVaultGraded(address: string, chain: string) {
    const res = await fetch(`${API_BASE}/v3/vaults/${address}?chain=${chain}`, { headers });
    return res.json();
  }

  async function fetchVaultOps(address: string, chain: string) {
    const res = await fetch(`${API_BASE}/vaults/${address}?chain=${chain}`, { headers });
    return res.json();
  }

  async function fetchDepegDetail(address: string, chain: string, hours = 24) {
    const res = await fetch(`${API_BASE}/rwa/${address}?chain=${chain}&hours=${hours}`, { headers });
    return res.json();
  }

  async function fetchRwaVaultGraded(address: string, chain: string) {
    const res = await fetch(`${API_BASE}/v3/rwa/${address}?chain=${chain}`, { headers });
    return res.json();
  }

  const WORSE_GRADES = ['C', 'C-', 'D', 'F'];
  const SCORE_DELTA_LIMIT = 15;
  const TVL_FLIGHT_LIMIT = -0.1;

  async function monitorBook(positions: Position[]) {
    const alerts: { position: Position; messages: string[] }[] = [];

    for (const position of positions) {
      const messages: string[] = [];

      if (position.type === 'vault') {
        const [graded, ops] = await Promise.all([
          fetchVaultGraded(position.address, position.chain),
          fetchVaultOps(position.address, position.chain),
        ]);

        // Fail-closed: stale or missing data is unknown risk, never safe
        if (ops.stale) {
          messages.push('Operational data is stale, treat as unknown risk');
        } else {
          if (WORSE_GRADES.includes(graded.composite.grade)) {
            messages.push(`Grade: ${graded.composite.grade} (score ${graded.composite.score}/100)`);
          }
          if (ops.metadata.tvl_flight_1w <= TVL_FLIGHT_LIMIT) {
            messages.push(`TVL flight (1w): ${ops.metadata.tvl_flight_1w}`);
          }
          if (ops.exit_recommendation || ops.attention_needed) {
            messages.push(`Curator flag, exit_recommendation: ${ops.exit_recommendation}, attention_needed: ${ops.attention_needed}`);
          }
        }
      }

      if (position.type === 'rwa_token') {
        const depeg = await fetchDepegDetail(position.address, position.chain);

        if (depeg.stale) {
          messages.push('Depeg data is stale, treat as unknown risk');
        } else {
          if (['watch', 'warning', 'critical'].includes(depeg.snapshot.tier)) {
            messages.push(`Depeg tier: ${depeg.snapshot.tier} (dev ${depeg.snapshot.dev_clean})`);
          }
          if (Math.abs(depeg.token.score_delta_24h) >= SCORE_DELTA_LIMIT) {
            messages.push(`24h score delta: ${depeg.token.score_delta_24h}`);
          }
        }
      }

      if (position.type === 'rwa_vault') {
        const rwaGraded = await fetchRwaVaultGraded(position.address, position.chain);
        if (WORSE_GRADES.includes(rwaGraded.composite.grade)) {
          messages.push(`RWA vault grade: ${rwaGraded.composite.grade} (score ${rwaGraded.composite.score}/100)`);
        }
      }

      if (messages.length > 0) {
        alerts.push({ position, messages });
      }
    }

    return alerts;
  }

  // Example: monitor an allocated book
  const alerts = await monitorBook([
    { type: 'vault', address: '0x0deFfd509197aAD5207d2A55862835b467E8128F', chain: 'eth' },
    { type: 'rwa_token', address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', chain: 'eth' },
    { type: 'rwa_token', address: '0xdAC17F958D2ee523a2206206994597C13D831ec7', chain: 'eth' },
  ]);

  for (const { position, messages } of alerts) {
    console.log(`[ALERT] ${position.type} ${position.address} (${position.chain})`);
    messages.forEach((m) => console.log(`  - ${m}`));
  }
  ```
</Accordion>

***

## Example Addresses for Testing

| Address                                      | Type           | Chain |
| -------------------------------------------- | -------------- | ----- |
| `0x0deFfd509197aAD5207d2A55862835b467E8128F` | ERC-4626 vault | eth   |
| `0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48` | USDC           | eth   |
| `0xdAC17F958D2ee523a2206206994597C13D831ec7` | USDT           | eth   |
| `0x6B175474E89094C44Da98b954EedeAC495271d0F` | DAI            | eth   |

***

## API Quick Reference

| Endpoint                                     | Use Case                                                                                    |
| -------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `GET /v3/vaults/{address}?chain={chain}`     | Graded vault rating (letter grade, stars, categories)                                       |
| `GET /vaults/{address}?chain={chain}`        | Operational vault detail (curator, utilization, liquidity, TVL flight, exit recommendation) |
| `GET /rwa/{address}?chain={chain}&hours={n}` | Stablecoin / RWA depeg snapshot, liquidity depth, score deltas                              |
| `GET /v3/rwa/{address}?chain={chain}`        | Graded RWA vault rating                                                                     |

**Authentication:**

```text theme={null}
Header: x-api-key: YOUR_API_KEY
Base URL: https://api.webacy.com
```

***

## Ready to Ship?

1. **Identify your book**: list every vault, stablecoin, and RWA position you currently hold, with chain and address
2. **Wire up the poll**: call the graded and operational endpoints on your chosen cadence and run them through `checkAlerts()`
3. **Go live**: ship the badge in your UI and route alerts to your team

## Next Steps

<CardGroup cols={2}>
  <Card title="New Asset Due Diligence" icon="magnifying-glass" href="/guides/new-asset-due-diligence">
    Screen a vault, stablecoin, or RWA before you allocate to it
  </Card>

  <Card title="Alerting with Webhooks" icon="bell" href="/essentials/webhooks">
    Subscribe to DEPEG\_TIER\_CHANGE events instead of polling on a timer
  </Card>

  <Card title="Monitor Stablecoin Depeg Risk" icon="chart-line" href="/guides/stablecoin-depeg-monitoring">
    The full depeg monitoring workflow across 600+ pegged tokens
  </Card>

  <Card title="Yield Scores" icon="gauge-high" href="/yield-scores">
    How raw APY gets discounted by issuer risk
  </Card>
</CardGroup>
