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.
Supported Events
| Event | Description |
|---|
entry.created | New entry added |
entry.updated | Entry modified |
entry.deleted | Entry removed |
Setting Up Your First Webhook
Open Helper Settings
Navigate to your helper → Settings (gear icon) → Integrations
Add Webhook URL
Enter your endpoint URL (must be HTTPS):https://your-server.com/api/peanuts-webhook
Select Events
Choose which events trigger the webhook:
- ✅ Entry created
- ✅ Entry updated
- ☐ Entry deleted
Save and Test
Click Save, then Send Test to verify connection.
Webhook Payload
When an event fires, Peanuts sends a POST request:
{
"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
Always verify webhook signatures in production to ensure requests come from Peanuts.
Peanuts signs every request with HMAC-SHA256. The signature is in the X-Peanuts-Signature header.
Finding Your Secret
- Go to helper Settings → Integrations
- Click Show Secret next to your webhook
- Copy for use in verification code
Verification Code
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 });
});
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
No-Code Integrations
Zapier
Choose Trigger
Select Webhooks by Zapier → Catch Hook
Copy URL
Zapier provides a unique webhook URL
Add to Peanuts
Paste URL in your helper’s webhook settings
Test and Build
Add a test entry, then build your action (Google Sheets, Slack, etc.)
Popular Zaps:
- Expense → Google Sheets row
- Task completed → Slack notification
- New entry → Airtable record
Make (Integromat)
Create Scenario
New scenario → Webhooks → Custom webhook
Get URL
Click Add to generate webhook URL
Connect
Add URL to Peanuts, send test entry
Redetermine Structure
Click Redetermine data structure in Make
n8n (Self-Hosted)
Webhook Node → Set webhook path → Copy production URL → Add to Peanuts
n8n can connect directly to Lovable via MCP for enhanced automation.
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
Respond Quickly
Return 200 immediately, process async. Timeout is 30 seconds.
Implement Idempotency
Use entry.id to detect duplicates and prevent double-processing.
Log Everything
Store raw payloads for debugging failed deliveries.
Use HTTPS
Only HTTPS endpoints are accepted for security.
Quick Response Pattern
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
- Verify webhook is enabled in settings
- Check URL is correct and accessible
- Ensure endpoint returns 2xx status
- Review webhook history for errors
Signature verification failing
- Use raw request body, not parsed JSON
- Check secret has no extra whitespace
- Verify SHA256, not SHA1
- Use timing-safe comparison
Implement idempotency:const processed = new Set();
if (processed.has(entry.id)) return;
processed.add(entry.id);
Exercise
Practice: Expense to Google Sheets
- Create a Zapier account (free tier works)
- Set up a “Catch Hook” trigger
- Add the webhook URL to your Expense Tracker
- Create action: Add row to Google Sheets
- Log 3 expenses and verify they appear in your sheet
Bonus: Add a filter to only sync expenses over $50
Key Takeaways
Remember: Webhooks enable real-time sync. Always verify signatures, respond quickly, and handle retries gracefully.
Next Steps