Webhooks Guide
Real-time event notifications from GreenMonkey to your application.
Overview
Webhooks allow your application to receive real-time notifications when events occur in GreenMonkey. Instead of polling our API, you can subscribe to events and we'll send HTTP POST requests to your endpoint.
Setting Up Webhooks
Step 1: Create Webhook Endpoint
Create an endpoint in your application to receive webhooks:
// Express.js example
app.post('/webhooks/greenmonkey', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-greenmonkey-signature'];
const timestamp = req.headers['x-greenmonkey-timestamp'];
const body = req.body;
// Verify webhook (see Security section)
if (!verifyWebhook(body, signature, timestamp)) {
return res.status(400).send('Invalid signature');
}
// Parse event
const event = JSON.parse(body);
// Handle event
switch (event.type) {
case 'purchase.completed':
handlePurchaseCompleted(event.data);
break;
case 'product.published':
handleProductPublished(event.data);
break;
// ... handle other events
}
// Acknowledge receipt
res.status(200).send('OK');
});
Step 2: Configure Webhook in Dashboard
- Go to Dashboard ā Settings ā Webhooks
- Click "Add Webhook Endpoint"
- Enter your endpoint URL
- Select events to subscribe to
- Copy the webhook secret
Step 3: Test Your Webhook
Use the test feature to verify your endpoint:
# GreenMonkey will send a test event
POST https://your-app.com/webhooks/greenmonkey
{
"id": "evt_test_123",
"type": "test",
"created": "2024-03-20T10:00:00Z",
"data": {
"message": "Test webhook from GreenMonkey"
}
}
Event Types
Product Events
product.published
Triggered when a new product is published.
{
"id": "evt_abc123",
"type": "product.published",
"created": "2024-03-20T10:00:00Z",
"data": {
"product": {
"id": "prod_123",
"title": "SEO Content Generator",
"type": "PROMPT",
"price": 29.99,
"authorId": "user_456"
}
}
}
product.updated
Triggered when a product is updated.
{
"id": "evt_def456",
"type": "product.updated",
"created": "2024-03-20T11:00:00Z",
"data": {
"product": {
"id": "prod_123",
"changes": {
"price": { "old": 29.99, "new": 24.99 },
"description": { "old": "...", "new": "..." }
}
}
}
}
product.unpublished
Triggered when a product is unpublished or deleted.
Purchase Events
purchase.completed
Triggered when a purchase is successful.
{
"id": "evt_ghi789",
"type": "purchase.completed",
"created": "2024-03-20T12:00:00Z",
"data": {
"order": {
"id": "ord_789",
"productId": "prod_123",
"buyerId": "user_101",
"amount": 24.99,
"currency": "USD"
}
}
}
purchase.refunded
Triggered when a refund is processed.
{
"id": "evt_jkl012",
"type": "purchase.refunded",
"created": "2024-03-20T13:00:00Z",
"data": {
"refund": {
"id": "ref_345",
"orderId": "ord_789",
"amount": 24.99,
"reason": "Customer request"
}
}
}
Payout Events
payout.completed
Triggered when a seller payout is sent.
{
"id": "evt_mno345",
"type": "payout.completed",
"created": "2024-03-20T14:00:00Z",
"data": {
"payout": {
"id": "pay_678",
"sellerId": "user_456",
"amount": 199.92,
"currency": "USD",
"period": {
"start": "2024-03-01",
"end": "2024-03-19"
}
}
}
}
Review Events
review.created
Triggered when a new review is posted.
{
"id": "evt_pqr678",
"type": "review.created",
"created": "2024-03-20T15:00:00Z",
"data": {
"review": {
"id": "rev_901",
"productId": "prod_123",
"rating": 5,
"comment": "Excellent prompt!",
"authorId": "user_101"
}
}
}
User Events
user.subscription.updated
Triggered when a user's subscription changes.
{
"id": "evt_stu901",
"type": "user.subscription.updated",
"created": "2024-03-20T16:00:00Z",
"data": {
"subscription": {
"userId": "user_101",
"oldPlan": "free",
"newPlan": "pro",
"effectiveDate": "2024-03-20T16:00:00Z"
}
}
}
Webhook Security
Signature Verification
Always verify webhook signatures to ensure requests are from GreenMonkey:
const crypto = require('crypto');
function verifyWebhook(payload, signature, timestamp, secret) {
// Check timestamp (prevent replay attacks)
const currentTime = Math.floor(Date.now() / 1000);
if (currentTime - parseInt(timestamp) > 300) {
// 5 minutes
return false;
}
// Create signature
const signedPayload = `${timestamp}.${payload}`;
const expectedSignature = crypto.createHmac('sha256', secret).update(signedPayload).digest('hex');
// Compare signatures
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature));
}
IP Allowlisting
For additional security, allowlist GreenMonkey's webhook IPs:
35.241.123.45
35.241.123.46
35.241.123.47
HTTPS Only
Always use HTTPS endpoints for webhooks:
- ā
https://your-app.com/webhooks
- ā
http://your-app.com/webhooks
Handling Webhooks
Idempotency
Webhooks may be sent multiple times. Handle idempotently:
const processedEvents = new Set();
async function handleWebhook(event) {
// Check if already processed
if (processedEvents.has(event.id)) {
console.log(`Event ${event.id} already processed`);
return;
}
// Process event
await processEvent(event);
// Mark as processed
processedEvents.add(event.id);
// Store in database for persistence
await db.processedWebhooks.create({
data: { eventId: event.id },
});
}
Async Processing
Process webhooks asynchronously to respond quickly:
app.post('/webhooks', async (req, res) => {
const event = req.body;
// Acknowledge immediately
res.status(200).send('OK');
// Process async
queue.add('process-webhook', { event });
});
// Worker
queue.process('process-webhook', async (job) => {
const { event } = job.data;
switch (event.type) {
case 'purchase.completed':
await sendThankYouEmail(event.data);
await updateInventory(event.data);
break;
// ... other handlers
}
});
Error Handling
Implement robust error handling:
async function handleWebhook(event) {
try {
await processEvent(event);
} catch (error) {
console.error('Webhook processing error:', error);
// Log to monitoring service
await logError({
type: 'webhook_error',
event: event.id,
error: error.message,
});
// Retry if appropriate
if (isRetryableError(error)) {
await retryQueue.add({ event, attempts: 1 });
}
}
}
Retry Logic
GreenMonkey implements automatic retries for failed webhooks:
Retry Schedule
- Immediate retry
- 1 minute
- 5 minutes
- 30 minutes
- 2 hours
- 6 hours
- 12 hours
- 24 hours
After 8 attempts, the webhook is marked as failed.
Handling Retries
app.post('/webhooks', (req, res) => {
const attemptNumber = req.headers['x-greenmonkey-attempt'];
if (attemptNumber > 1) {
console.log(`Retry attempt ${attemptNumber} for event ${req.body.id}`);
}
// Process webhook...
});
Development & Testing
Local Development
Use ngrok for local webhook testing:
# Install ngrok
npm install -g ngrok
# Expose local server
ngrok http 3000
# Use the HTTPS URL for webhooks
# https://abc123.ngrok.io/webhooks
Webhook CLI
Test webhooks with our CLI:
# Install CLI
npm install -g @greenmonkey/cli
# Send test event
greenmonkey webhooks test \
--event purchase.completed \
--endpoint https://your-app.com/webhooks
Mock Events
Generate mock events for testing:
const mockEvents = {
'purchase.completed': {
id: 'evt_test_123',
type: 'purchase.completed',
created: new Date().toISOString(),
data: {
order: {
id: 'ord_test_123',
productId: 'prod_test_123',
buyerId: 'user_test_123',
amount: 29.99,
currency: 'USD',
},
},
},
};
// Test your handler
handleWebhook(mockEvents['purchase.completed']);
Monitoring & Debugging
Webhook Logs
View webhook delivery logs in the dashboard:
- Go to Dashboard ā Settings ā Webhooks
- Click on your endpoint
- View delivery history
- See request/response details
Debug Information
Each webhook includes debug headers:
X-GreenMonkey-Event-ID: evt_abc123
X-GreenMonkey-Timestamp: 1679308800
X-GreenMonkey-Signature: sha256=abc...
X-GreenMonkey-Attempt: 1
Common Issues
Webhook not received
- Verify endpoint URL is correct
- Check server logs for errors
- Ensure HTTPS is working
- Check firewall rules
Signature verification failing
- Ensure using raw body (not parsed)
- Verify secret is correct
- Check timestamp validation
Duplicate events
- Implement idempotency
- Check for event ID in database
- Handle gracefully
Best Practices
Do's ā
- Verify signatures - Always validate webhook authenticity
- Respond quickly - Return 200 OK immediately
- Process async - Handle processing in background
- Be idempotent - Handle duplicate events gracefully
- Log everything - Keep detailed logs for debugging
- Monitor failures - Alert on webhook processing errors
Don'ts ā
- Don't trust blindly - Always verify signatures
- Don't process synchronously - Will timeout
- Don't ignore retries - Handle them properly
- Don't expose secrets - Keep webhook secret secure
- Don't ignore errors - Log and monitor failures
Example Implementations
Node.js + Express
const express = require('express');
const crypto = require('crypto');
const app = express();
const WEBHOOK_SECRET = process.env.GREENMONKEY_WEBHOOK_SECRET;
app.post('/webhooks/greenmonkey', express.raw({ type: 'application/json' }), async (req, res) => {
// Verify webhook
const signature = req.headers['x-greenmonkey-signature'];
if (!verifySignature(req.body, signature, WEBHOOK_SECRET)) {
return res.status(400).send('Invalid signature');
}
// Parse and handle
const event = JSON.parse(req.body);
await handleEvent(event);
res.status(200).send('OK');
});
Python + Flask
from flask import Flask, request, abort
import hmac
import hashlib
import json
app = Flask(__name__)
WEBHOOK_SECRET = os.environ['GREENMONKEY_WEBHOOK_SECRET']
@app.route('/webhooks/greenmonkey', methods=['POST'])
def handle_webhook():
# Verify signature
signature = request.headers.get('X-GreenMonkey-Signature')
if not verify_signature(request.data, signature, WEBHOOK_SECRET):
abort(400)
# Parse event
event = json.loads(request.data)
# Handle event
handle_event(event)
return 'OK', 200
def verify_signature(payload, signature, secret):
expected = hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
Ruby + Rails
class WebhooksController < ApplicationController
skip_before_action :verify_authenticity_token
def greenmonkey
# Verify signature
signature = request.headers['X-GreenMonkey-Signature']
unless verify_signature(request.body.read, signature)
return head :bad_request
end
# Parse and handle
event = JSON.parse(request.body.read)
WebhookJob.perform_later(event)
head :ok
end
private
def verify_signature(payload, signature)
expected = OpenSSL::HMAC.hexdigest(
'SHA256',
ENV['GREENMONKEY_WEBHOOK_SECRET'],
payload
)
Rack::Utils.secure_compare(expected, signature)
end
end
Next Steps
- Explore API endpoints for webhook event details
- Implement authentication for secure integration
- Use SDKs for easier webhook handling