> ## 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.

# Webhooks & Automations

> Connect Peanuts to external systems with real-time webhooks

<Info>
  **Time:** 20 minutes | **Level:** Expert\
  **Prerequisites:** [Sharing & Collaboration](/tutorials/intermediate/sharing-and-collaboration), [Scheduled Reports](/tutorials/advanced/scheduled-reports)\
  **Requires:** Pro plan or higher
</Info>

## What You'll Learn

* Configure webhooks for real-time data sync
* Verify webhook signatures for security
* Build automations with Zapier, Make, and n8n
* Handle retries and error scenarios

***

## Understanding Webhooks

Webhooks let Peanuts **push** data to your systems instantly when events happen—no polling required.

```mermaid theme={null}
sequenceDiagram
    participant User
    participant Peanuts
    participant Your Server
    
    User->>Peanuts: Creates entry
    Peanuts->>Your Server: POST /webhook
    Your Server->>Peanuts: 200 OK
    Your Server->>Your Server: Process data
```

### Supported Events

| Event           | Description     |
| --------------- | --------------- |
| `entry.created` | New entry added |
| `entry.updated` | Entry modified  |
| `entry.deleted` | Entry removed   |

***

## Setting Up Your First Webhook

<Steps>
  <Step title="Open Helper Settings">
    Navigate to your helper → **Settings** (gear icon) → **Integrations**
  </Step>

  <Step title="Add Webhook URL">
    Enter your endpoint URL (must be HTTPS):

    ```
    https://your-server.com/api/peanuts-webhook
    ```
  </Step>

  <Step title="Select Events">
    Choose which events trigger the webhook:

    * ✅ Entry created
    * ✅ Entry updated
    * ☐ Entry deleted
  </Step>

  <Step title="Save and Test">
    Click **Save**, then **Send Test** to verify connection.
  </Step>
</Steps>

***

## Webhook Payload

When an event fires, Peanuts sends a POST request:

```json theme={null}
{
  "event": "entry.created",
  "timestamp": "2026-01-15T14:30:00Z",
  "helper": {
    "id": "abc123",
    "name": "Expense Tracker",
    "command": "/expenses"
  },
  "entry": {
    "id": "entry_456",
    "created_at": "2026-01-15T14:30:00Z",
    "data": {
      "amount": 45.50,
      "category": "Food",
      "description": "Team lunch"
    }
  },
  "user": {
    "id": "user_789"
  }
}
```

***

## Security: Signature Verification

<Warning>
  Always verify webhook signatures in production to ensure requests come from Peanuts.
</Warning>

Peanuts signs every request with HMAC-SHA256. The signature is in the `X-Peanuts-Signature` header.

### Finding Your Secret

1. Go to helper **Settings** → **Integrations**
2. Click **Show Secret** next to your webhook
3. Copy for use in verification code

### Verification Code

<Tabs>
  <Tab title="Node.js">
    ```javascript theme={null}
    const crypto = require('crypto');

    function verifyWebhook(req, secret) {
      const signature = req.headers['x-peanuts-signature'];
      const payload = JSON.stringify(req.body);
      
      const expected = crypto
        .createHmac('sha256', secret)
        .update(payload)
        .digest('hex');
      
      return crypto.timingSafeEqual(
        Buffer.from(signature),
        Buffer.from(expected)
      );
    }

    // Express middleware
    app.post('/webhook', (req, res) => {
      if (!verifyWebhook(req, process.env.PEANUTS_SECRET)) {
        return res.status(401).json({ error: 'Invalid signature' });
      }
      
      const { event, entry } = req.body;
      console.log(`Received ${event}:`, entry.data);
      
      res.status(200).json({ received: true });
    });
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import hmac
    import hashlib
    from flask import Flask, request, jsonify

    def verify_webhook(payload, signature, secret):
        expected = hmac.new(
            secret.encode(),
            payload,
            hashlib.sha256
        ).hexdigest()
        return hmac.compare_digest(signature, expected)

    @app.route('/webhook', methods=['POST'])
    def webhook():
        signature = request.headers.get('X-Peanuts-Signature')
        secret = os.environ.get('PEANUTS_SECRET')
        
        if not verify_webhook(request.data, signature, secret):
            return jsonify({'error': 'Invalid signature'}), 401
        
        data = request.json
        print(f"Received {data['event']}")
        
        return jsonify({'received': True}), 200
    ```
  </Tab>
</Tabs>

***

## No-Code Integrations

### Zapier

<Steps>
  <Step title="Create a Zap">
    Go to [zapier.com](https://zapier.com) → Create Zap
  </Step>

  <Step title="Choose Trigger">
    Select **Webhooks by Zapier** → **Catch Hook**
  </Step>

  <Step title="Copy URL">
    Zapier provides a unique webhook URL
  </Step>

  <Step title="Add to Peanuts">
    Paste URL in your helper's webhook settings
  </Step>

  <Step title="Test and Build">
    Add a test entry, then build your action (Google Sheets, Slack, etc.)
  </Step>
</Steps>

**Popular Zaps:**

* Expense → Google Sheets row
* Task completed → Slack notification
* New entry → Airtable record

### Make (Integromat)

<Steps>
  <Step title="Create Scenario">
    New scenario → **Webhooks** → **Custom webhook**
  </Step>

  <Step title="Get URL">
    Click **Add** to generate webhook URL
  </Step>

  <Step title="Connect">
    Add URL to Peanuts, send test entry
  </Step>

  <Step title="Redetermine Structure">
    Click **Redetermine data structure** in Make
  </Step>
</Steps>

### n8n (Self-Hosted)

```
Webhook Node → Set webhook path → Copy production URL → Add to Peanuts
```

<Tip>
  n8n can connect directly to Lovable via MCP for enhanced automation.
</Tip>

***

## Retry Policy

Failed webhooks retry automatically:

| Attempt   | Delay      |
| --------- | ---------- |
| 1st retry | 1 minute   |
| 2nd retry | 5 minutes  |
| 3rd retry | 30 minutes |

After 3 failures, the webhook is marked as failed. Check **View History** for details.

***

## Best Practices

<CardGroup cols={2}>
  <Card title="Respond Quickly" icon="bolt">
    Return 200 immediately, process async. Timeout is 30 seconds.
  </Card>

  <Card title="Implement Idempotency" icon="shield">
    Use `entry.id` to detect duplicates and prevent double-processing.
  </Card>

  <Card title="Log Everything" icon="file">
    Store raw payloads for debugging failed deliveries.
  </Card>

  <Card title="Use HTTPS" icon="lock">
    Only HTTPS endpoints are accepted for security.
  </Card>
</CardGroup>

### Quick Response Pattern

```javascript theme={null}
app.post('/webhook', async (req, res) => {
  // Verify first
  if (!verifyWebhook(req, secret)) {
    return res.status(401).send();
  }
  
  // Queue for async processing
  await queue.add('process-entry', req.body);
  
  // Respond immediately
  res.status(200).json({ queued: true });
});
```

***

## Troubleshooting

<AccordionGroup>
  <Accordion title="Webhooks not firing">
    1. Verify webhook is enabled in settings
    2. Check URL is correct and accessible
    3. Ensure endpoint returns 2xx status
    4. Review webhook history for errors
  </Accordion>

  <Accordion title="Signature verification failing">
    1. Use raw request body, not parsed JSON
    2. Check secret has no extra whitespace
    3. Verify SHA256, not SHA1
    4. Use timing-safe comparison
  </Accordion>

  <Accordion title="Duplicate webhooks">
    Implement idempotency:

    ```javascript theme={null}
    const processed = new Set();
    if (processed.has(entry.id)) return;
    processed.add(entry.id);
    ```
  </Accordion>
</AccordionGroup>

***

## Exercise

<Card title="Practice: Expense to Google Sheets" icon="dumbbell">
  1. Create a Zapier account (free tier works)
  2. Set up a "Catch Hook" trigger
  3. Add the webhook URL to your Expense Tracker
  4. Create action: Add row to Google Sheets
  5. Log 3 expenses and verify they appear in your sheet

  **Bonus:** Add a filter to only sync expenses over \$50
</Card>

***

## Key Takeaways

<Tip>
  **Remember:** Webhooks enable real-time sync. Always verify signatures, respond quickly, and handle retries gracefully.
</Tip>

***

## Next Steps

<CardGroup cols={2}>
  <Card title="API Integration" href="/tutorials/expert/api-integration" icon="code">
    Direct API access for custom apps
  </Card>

  <Card title="Enterprise Patterns" href="/tutorials/expert/enterprise-patterns" icon="building">
    Scale Peanuts across teams
  </Card>
</CardGroup>
