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

# API Integration

> Build custom applications using the Peanuts API

<Info>
  **Time:** 25 minutes | **Level:** Expert\
  **Prerequisites:** [Webhooks & Automations](/tutorials/expert/webhooks-and-automations)\
  **Requires:** Pro plan or higher
</Info>

## What You'll Learn

* Understand Peanuts' push-based architecture
* Build custom integrations using webhooks
* Structure data for programmatic access
* Design patterns for reliable integrations

***

## Architecture Overview

Peanuts uses a **push-based** architecture rather than a traditional REST API:

```mermaid theme={null}
flowchart LR
    A[Your App] -->|Configure| B[Peanuts Helper]
    B -->|Entry Created| C[Webhook]
    C -->|POST| D[Your Server]
    D -->|Process| E[Your Database]
```

<Info>
  **Why push-based?** Real-time updates, no polling overhead, and simpler integration for most use cases.
</Info>

***

## Webhook as API

Your webhook endpoint becomes your API receiver. Structure your handler to process different event types:

```typescript theme={null}
interface WebhookEvent {
  event: 'entry.created' | 'entry.updated' | 'entry.deleted';
  timestamp: string;
  helper: {
    id: string;
    name: string;
    command: string;
  };
  entry: {
    id: string;
    created_at: string;
    data: Record<string, unknown>;
  };
  user: {
    id: string;
  };
}

// Express handler
app.post('/api/peanuts', (req, res) => {
  const event: WebhookEvent = req.body;
  
  switch (event.event) {
    case 'entry.created':
      handleNewEntry(event);
      break;
    case 'entry.updated':
      handleUpdatedEntry(event);
      break;
    case 'entry.deleted':
      handleDeletedEntry(event);
      break;
  }
  
  res.status(200).json({ processed: true });
});
```

***

## Type Definitions

Use these TypeScript types for type-safe integrations:

```typescript theme={null}
// Core types
interface HelperInfo {
  id: string;
  name: string;
  command: string;
}

interface EntryData {
  id: string;
  created_at: string;
  data: {
    [fieldId: string]: string | number | boolean | string[];
  };
}

interface WebhookPayload {
  event: string;
  timestamp: string;
  helper: HelperInfo;
  entry: EntryData;
  user: { id: string };
}

// Signature verification
function verifySignature(
  payload: string,
  signature: string,
  secret: string
): boolean {
  const crypto = require('crypto');
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}
```

***

## Integration Patterns

### Pattern 1: Data Sync

Sync Peanuts entries to your database in real-time:

```typescript theme={null}
async function handleNewEntry(event: WebhookEvent) {
  const { entry, helper } = event;
  
  // Map Peanuts fields to your schema
  const record = {
    external_id: entry.id,
    source: 'peanuts',
    helper_name: helper.name,
    amount: entry.data.amount,
    category: entry.data.category,
    description: entry.data.description,
    logged_at: entry.created_at,
    synced_at: new Date().toISOString()
  };
  
  await db.expenses.upsert({
    where: { external_id: entry.id },
    create: record,
    update: record
  });
}
```

### Pattern 2: Notifications

Send alerts based on entry conditions:

```typescript theme={null}
async function handleNewEntry(event: WebhookEvent) {
  const { entry, helper } = event;
  
  // High-value expense alert
  if (helper.command === '/expenses' && entry.data.amount > 500) {
    await slack.send({
      channel: '#finance-alerts',
      text: `🚨 High expense: $${entry.data.amount} - ${entry.data.description}`
    });
  }
  
  // Task deadline warning
  if (helper.command === '/tasks' && entry.data.due_date) {
    const dueDate = new Date(entry.data.due_date);
    const now = new Date();
    const daysUntilDue = Math.ceil((dueDate - now) / (1000 * 60 * 60 * 24));
    
    if (daysUntilDue <= 1) {
      await sendUrgentNotification(entry.data.title);
    }
  }
}
```

### Pattern 3: Aggregation Service

Build dashboards by aggregating webhook data:

```typescript theme={null}
// In-memory aggregation (use Redis for production)
const dailyStats = new Map<string, number>();

async function handleNewEntry(event: WebhookEvent) {
  if (event.helper.command !== '/expenses') return;
  
  const date = event.entry.created_at.split('T')[0];
  const amount = Number(event.entry.data.amount) || 0;
  
  const current = dailyStats.get(date) || 0;
  dailyStats.set(date, current + amount);
  
  // Expose via API
  console.log(`Daily total for ${date}: $${dailyStats.get(date)}`);
}

// Dashboard endpoint
app.get('/api/dashboard', (req, res) => {
  res.json({
    daily_totals: Object.fromEntries(dailyStats),
    total: [...dailyStats.values()].reduce((a, b) => a + b, 0)
  });
});
```

***

## Building a Custom Client

Create a client library for your integration:

```typescript theme={null}
class PeanutsClient {
  private webhookSecret: string;
  private handlers: Map<string, Function[]> = new Map();
  
  constructor(secret: string) {
    this.webhookSecret = secret;
  }
  
  // Register event handlers
  on(event: string, handler: Function) {
    const existing = this.handlers.get(event) || [];
    this.handlers.set(event, [...existing, handler]);
  }
  
  // Verify and process incoming webhooks
  async handleWebhook(req: Request): Promise<boolean> {
    const signature = req.headers['x-peanuts-signature'] as string;
    const payload = JSON.stringify(req.body);
    
    if (!this.verifySignature(payload, signature)) {
      throw new Error('Invalid signature');
    }
    
    const event = req.body as WebhookEvent;
    const handlers = this.handlers.get(event.event) || [];
    
    for (const handler of handlers) {
      await handler(event);
    }
    
    return true;
  }
  
  private verifySignature(payload: string, signature: string): boolean {
    const crypto = require('crypto');
    const expected = crypto
      .createHmac('sha256', this.webhookSecret)
      .update(payload)
      .digest('hex');
    return signature === expected;
  }
}

// Usage
const peanuts = new PeanutsClient(process.env.PEANUTS_SECRET!);

peanuts.on('entry.created', async (event) => {
  console.log('New entry:', event.entry.data);
});

app.post('/webhook', async (req, res) => {
  try {
    await peanuts.handleWebhook(req);
    res.status(200).json({ success: true });
  } catch (err) {
    res.status(401).json({ error: err.message });
  }
});
```

***

## Error Handling

Build resilient integrations:

```typescript theme={null}
async function processWebhook(event: WebhookEvent) {
  const maxRetries = 3;
  let attempt = 0;
  
  while (attempt < maxRetries) {
    try {
      await syncToDatabase(event);
      await sendNotifications(event);
      return; // Success
    } catch (error) {
      attempt++;
      console.error(`Attempt ${attempt} failed:`, error);
      
      if (attempt < maxRetries) {
        // Exponential backoff
        await sleep(Math.pow(2, attempt) * 1000);
      }
    }
  }
  
  // All retries failed - queue for manual review
  await deadLetterQueue.add(event);
}
```

***

## Testing Your Integration

### Local Development

Use [ngrok](https://ngrok.com) to expose your local server:

```bash theme={null}
# Terminal 1: Start your server
npm run dev

# Terminal 2: Expose with ngrok
ngrok http 3000
# Copy the https:// URL to Peanuts webhook settings
```

### Mock Payloads

Test with sample data:

```typescript theme={null}
const mockEntry = {
  event: 'entry.created',
  timestamp: new Date().toISOString(),
  helper: {
    id: 'test-helper',
    name: 'Test Tracker',
    command: '/test'
  },
  entry: {
    id: 'entry-123',
    created_at: new Date().toISOString(),
    data: {
      amount: 99.99,
      category: 'Test',
      description: 'Mock entry for testing'
    }
  },
  user: { id: 'user-456' }
};

// Test your handler
await handleNewEntry(mockEntry);
```

***

## Rate Limits & Best Practices

<CardGroup cols={2}>
  <Card title="Respond Fast" icon="bolt">
    Return 200 within 30 seconds. Process heavy work async.
  </Card>

  <Card title="Idempotent Handlers" icon="repeat">
    Same entry ID should produce same result. Use upserts.
  </Card>

  <Card title="Log Raw Payloads" icon="file-lines">
    Store raw JSON for debugging failed syncs.
  </Card>

  <Card title="Monitor Failures" icon="chart-line">
    Track webhook success rates. Alert on spikes.
  </Card>
</CardGroup>

***

## Exercise

<Card title="Practice: Build an Expense Dashboard" icon="dumbbell">
  Create a simple dashboard that:

  1. Receives expense webhooks from Peanuts
  2. Stores entries in a local JSON file or SQLite
  3. Exposes `/api/stats` endpoint with:
     * Total expenses this month
     * Count by category
     * Average expense amount
  4. Test with 5+ expense entries

  **Stack suggestion:** Node.js + Express + lowdb (JSON database)
</Card>

***

## Key Takeaways

<Tip>
  **Remember:** Peanuts pushes data to you via webhooks. Build idempotent handlers, respond quickly, and queue heavy processing for async execution.
</Tip>

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Enterprise Patterns" href="/tutorials/expert/enterprise-patterns" icon="building">
    Scale across teams and departments
  </Card>

  <Card title="Building Client Tools" href="/tutorials/expert/building-client-tools" icon="users">
    Create self-service tools for clients
  </Card>
</CardGroup>
