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

# Track & Positions

> Read wallet positions and follow the action lifecycle via polling, webhooks, or WebSocket

This example shows the standalone helpers used across every Pods flow: reading a wallet's positions,
and following an action to completion by **polling**, **webhook**, or **WebSocket**.

## Overview

This example covers:

* **Positions** — `GET /v2/wallets/{address}?include=earn`
* **Poll an action** — `GET /actions/{id}` (`INITIAL → PENDING → SUCCESS/FAILED/REFUNDED/EXPIRED`)
* **Webhook** — receive `ACTION_UPDATE` events (push)
* **WebSocket** — subscribe to `wss://api.pods.finance/updates` (push)

## Prerequisites

* Node.js 18+
* Pods API key
* For webhook/WebSocket modes: `npm install express ws`

## Installation

```bash theme={null}
npm install axios
# for push modes:
npm install express ws
```

## Environment Variables

```bash theme={null}
PODS_API_KEY=your-api-key
WALLET_ADDRESS=0x...
ACTION_ID=65f1a2b3c4d5e6f789012345
PORT=3000
```

## Step-by-Step Walkthrough

### 1. Check Positions

```javascript theme={null}
import axios from 'axios'

const pods = axios.create({
  baseURL: 'https://api.pods.finance',
  headers: { 'x-api-key': process.env.PODS_API_KEY, 'Content-Type': 'application/json' }
})

const { data } = await pods.get(`/v2/wallets/${process.env.WALLET_ADDRESS}`, {
  params: { include: 'earn' }
})

data.earn.positions.forEach(position => {
  const p = position.spotPosition
  console.log(position.strategy.id, '→ $' + p.underlyingBalanceUSD)
})
console.log('Total: $' + data.earn.summary.totalUnderlyingBalanceUSD)
```

### 2. Poll an Action

```javascript theme={null}
async function trackAction (id) {
  const terminal = ['SUCCESS', 'FAILED', 'REFUNDED', 'EXPIRED']
  while (true) {
    const { data: action } = await pods.get(`/actions/${id}`)
    console.log('status:', action.status)
    if (terminal.includes(action.status)) return action.status
    await new Promise(resolve => setTimeout(resolve, 5000))
  }
}
```

### 3. Webhook (push alternative)

Configure your customer webhook URL with Pods (or pass `webhookURL` per quote). Deframe **signs
every delivery** — verify the signature over the raw body before trusting it, then consume the
`{ messageType, data }` envelope (this is different from the WebSocket `{ type, action }` shape).
Ack quickly and do heavy work asynchronously.

```javascript theme={null}
import express from 'express'
import { createHmac, timingSafeEqual } from 'node:crypto'

const SECRET = process.env.WEBHOOK_SIGNING_SECRET // shared secret from Pods
const MAX_AGE_SECONDS = 300 // replay protection
const seenWebhookIds = new Set() // dedupe redelivered webhooks

// v1 HMAC over `${timestamp}.${webhookId}.${rawBody}`, compared timing-safe.
function verifySignature (req) {
  const webhookId = req.get('X-Deframe-Webhook-Id')
  const timestamp = req.get('X-Deframe-Webhook-Timestamp')
  const signatureHeader = req.get('X-Deframe-Webhook-Signature') // "v1=<hex>"
  if (!webhookId || !timestamp || !signatureHeader) return false

  const ageSeconds = Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp))
  if (!Number.isFinite(ageSeconds) || ageSeconds > MAX_AGE_SECONDS) return false

  const signedPayload = `${timestamp}.${webhookId}.${req.body.toString('utf8')}`
  const expected = createHmac('sha256', SECRET).update(signedPayload, 'utf8').digest('hex')
  const provided = signatureHeader.replace(/^v1=/, '')
  const a = Buffer.from(expected, 'hex')
  const b = Buffer.from(provided, 'hex')
  return a.length === b.length && timingSafeEqual(a, b)
}

const app = express()

// Raw body so the HMAC matches the exact bytes Deframe signed.
app.post('/webhooks/pods', express.raw({ type: '*/*' }), (req, res) => {
  if (!verifySignature(req)) return res.sendStatus(401)

  const webhookId = req.get('X-Deframe-Webhook-Id')
  if (seenWebhookIds.has(webhookId)) return res.sendStatus(200) // duplicate
  seenWebhookIds.add(webhookId)

  const event = JSON.parse(req.body.toString('utf8'))
  if (event.messageType === 'ACTION_UPDATE') {
    const action = event.data
    console.log('ACTION_UPDATE', action?.id, action?.status)
  }
  res.sendStatus(200)
})

app.listen(process.env.PORT ?? 3000)
```

### 4. WebSocket (push alternative)

Connect, subscribe to the wallet channel, and handle `action_update` events. Re-subscribe on
reconnect.

```javascript theme={null}
import WebSocket from 'ws'

const ws = new WebSocket('wss://api.pods.finance/updates')

ws.on('open', () => {
  ws.send(JSON.stringify({ type: 'subscribe', channel: process.env.WALLET_ADDRESS }))
})

ws.on('message', raw => {
  const event = JSON.parse(raw.toString())
  if (event.type === 'action_update') {
    console.log('action_update', event.action?.id, event.action?.status)
  }
})
```

## Running the Example

```bash theme={null}
node track-and-positions.js positions   # or: track | webhook | ws
```

See the full runnable script: [`track-and-positions.js`](/examples/javascript/track-and-positions.js).

## Next Steps

<CardGroup cols={2}>
  <Card title="Strategy Deposit" icon="arrow-up-right-dots" href="/examples/javascript/strategy-deposit">
    Deposit into yield strategies
  </Card>

  <Card title="Ondo Global Markets" icon="chart-line" href="/examples/javascript/ondo">
    Buy/sell tokenized stocks with async orders
  </Card>

  <Card title="Check Positions Guide" icon="wallet" href="/guides/yield/check-positions">
    Full wallet/position response shape
  </Card>

  <Card title="API Reference" icon="code" href="/api-reference/introduction">
    View complete API documentation
  </Card>
</CardGroup>
