Webhook Examples
This page provides comprehensive examples of how to implement webhook endpoints to receive real-time notifications from the ReviewData Lite API. These examples match the functionality shown in the interactive playground.
Base URL
All API requests are made to: https://data.reviewdata.ai
Webhook Configuration
- JavaScript (Node.js)
- Python
- PHP
const axios = require('axios');
const configureWebhook = async (apiKey, webhookUrl) => {
try {
const response = await axios.post('https://data.reviewdata.ai/webhooks/create-webhook/', {
api_key: apiKey,
webhook: webhookUrl
}, {
headers: {
'Content-Type': 'application/json'
}
});
console.log('Webhook configured successfully:');
console.log('Status:', response.data.status);
console.log('Webhook URL:', response.data.webhook);
return response.data;
} catch (error) {
console.error('Error configuring webhook:', error.response?.data || error.message);
throw error;
}
};
// Usage with playground default API key
configureWebhook('YOUR_API_KEY_HERE', 'https://your-domain.com/webhook');
import requests
def configure_webhook(api_key, webhook_url):
url = 'https://data.reviewdata.ai/webhooks/create-webhook/'
payload = {
'api_key': api_key,
'webhook': webhook_url
}
headers = {
'Content-Type': 'application/json'
}
try:
response = requests.post(url, json=payload, headers=headers)
response.raise_for_status()
result = response.json()
print('Webhook configured successfully:')
print(f'Status: {result.get("status")}')
print(f'Webhook URL: {result.get("webhook")}')
return result
except requests.exceptions.RequestException as e:
print('Error configuring webhook:', e)
if hasattr(e, 'response') and e.response is not None:
print('Response:', e.response.json())
raise
# Usage with playground default API key
if __name__ == "__main__":
configure_webhook('YOUR_API_KEY_HERE', 'https://your-domain.com/webhook')
<?php
function configureWebhook($apiKey, $webhookUrl) {
$url = 'https://data.reviewdata.ai/webhooks/create-webhook/';
$payload = [
'api_key' => $apiKey,
'webhook' => $webhookUrl
];
$options = [
'http' => [
'header' => "Content-type: application/json\r\n",
'method' => 'POST',
'content' => json_encode($payload)
]
];
$context = stream_context_create($options);
try {
$response = file_get_contents($url, false, $context);
if ($response === false) {
throw new Exception('Failed to make request');
}
$result = json_decode($response, true);
echo "Webhook configured successfully:\n";
echo "Status: " . ($result['status'] ?? 'N/A') . "\n";
echo "Webhook URL: " . ($result['webhook'] ?? 'N/A') . "\n";
return $result;
} catch (Exception $e) {
echo 'Error configuring webhook: ' . $e->getMessage() . "\n";
throw $e;
}
}
// Usage with playground default API key
configureWebhook('YOUR_API_KEY_HERE', 'https://your-domain.com/webhook');
?>
Webhook Endpoint Implementation
- Node.js with Express
- Python with Flask
- PHP
const express = require('express');
const app = express();
// Middleware to parse JSON
app.use(express.json());
// Webhook endpoint
app.post('/webhook', async (req, res) => {
try {
const { task_id, publisher_key, foreign_key, profile_key, task_status, reviews_urls } = req.body;
console.log(`Received webhook for task: ${task_id}`);
console.log(`Publisher: ${publisher_key}`);
console.log(`Foreign key: ${foreign_key}`);
console.log(`Task status: ${task_status}`);
// Handle different task statuses
if (task_status === 200) {
await handleTaskCompleted(req.body);
} else if (task_status === 100) {
await handleTaskQueued(req.body);
} else if (task_status === 101) {
await handleTaskInProgress(req.body);
} else if (task_status >= 400) {
await handleTaskFailed(req.body);
} else {
console.warn(`Unknown task status: ${task_status}`);
}
// Return success response
res.status(200).json({ status: 'success', message: 'Webhook processed' });
} catch (error) {
console.error('Webhook processing error:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Event handlers
async function handleTaskQueued(data) {
console.log(`Task ${data.task_id} queued for processing`);
console.log(`Publisher: ${data.publisher_key}, Foreign key: ${data.foreign_key}`);
// Update your database with queued status
await updateTaskStatus(data.task_id, 'queued', data);
}
async function handleTaskInProgress(data) {
console.log(`Task ${data.task_id} in progress for ${data.publisher_key}`);
// Update your database with in-progress status
await updateTaskStatus(data.task_id, 'in_progress', data);
// Send notification to users
await sendNotification(`Review collection in progress for ${data.foreign_key}`);
}
async function handleTaskCompleted(data) {
console.log(`Task ${data.task_id} completed successfully`);
console.log(`Reviews URLs: ${data.reviews_urls?.length || 0} files`);
// Store completed task data
const taskData = {
taskId: data.task_id,
foreignKey: data.foreign_key,
publisherKey: data.publisher_key,
profileKey: data.profile_key,
status: 'completed',
taskStatus: data.task_status,
reviewsUrls: data.reviews_urls || [],
completedAt: new Date().toISOString()
};
// Store in database
await storeCompletedTask(taskData);
// Send completion notification
await sendNotification(`Task completed: Reviews available for ${data.foreign_key}`);
// Optionally download and process reviews
if (data.reviews_urls && data.reviews_urls.length > 0) {
await downloadAndProcessReviews(data.reviews_urls, data.task_id);
}
}
async function handleTaskFailed(data) {
console.error(`Task ${data.task_id} failed with status ${data.task_status}`);
// Log error details
await logError({
taskId: data.task_id,
foreignKey: data.foreign_key,
publisherKey: data.publisher_key,
profileKey: data.profile_key,
taskStatus: data.task_status,
timestamp: new Date().toISOString()
});
// Send alert to administrators
await sendAlert(`Task failed: ${data.task_id} (Status: ${data.task_status})`);
}
// Helper functions (implement based on your needs)
async function updateTaskStatus(taskId, status, data) {
console.log(`Updating task ${taskId} status to ${status}`);
// Your database logic here
}
async function storeCompletedTask(taskData) {
console.log('Storing completed task:', taskData);
// Your database logic here
}
async function downloadAndProcessReviews(reviewsUrls, taskId) {
console.log(`Downloading reviews for task ${taskId} from ${reviewsUrls.length} URLs`);
// Your download and processing logic here
}
async function sendNotification(message) {
console.log('Sending notification:', message);
// Your notification logic here
}
async function sendAlert(message) {
console.log('Sending alert:', message);
// Your alerting logic here
}
async function logError(errorData) {
console.error('Logging error:', errorData);
// Your logging logic here
}
// Start server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Webhook server running on port ${PORT}`);
});
from flask import Flask, request, jsonify
import json
from datetime import datetime
app = Flask(__name__)
@app.route('/webhook', methods=['POST'])
def webhook():
try:
# Get webhook data
data = request.get_json()
task_id = data.get('task_id')
publisher_key = data.get('publisher_key')
foreign_key = data.get('foreign_key')
profile_key = data.get('profile_key')
task_status = data.get('task_status')
reviews_urls = data.get('reviews_urls', [])
print(f"Received webhook for task: {task_id}")
print(f"Publisher: {publisher_key}")
print(f"Foreign key: {foreign_key}")
print(f"Task status: {task_status}")
# Handle different task statuses
if task_status == 200:
handle_task_completed(data)
elif task_status == 100:
handle_task_queued(data)
elif task_status == 101:
handle_task_in_progress(data)
elif task_status >= 400:
handle_task_failed(data)
else:
print(f"Unknown task status: {task_status}")
return jsonify({'status': 'success', 'message': 'Webhook processed'}), 200
except Exception as e:
print(f"Webhook processing error: {e}")
return jsonify({'error': 'Internal server error'}), 500
def handle_task_queued(data):
"""Handle task queued status"""
print(f"Task {data['task_id']} queued for processing")
print(f"Publisher: {data['publisher_key']}, Foreign key: {data['foreign_key']}")
# Update your database with queued status
update_task_status(data['task_id'], 'queued', data)
def handle_task_in_progress(data):
"""Handle task in progress status"""
print(f"Task {data['task_id']} in progress for {data['publisher_key']}")
# Update your database with in-progress status
update_task_status(data['task_id'], 'in_progress', data)
# Send notification to users
send_notification(f"Review collection in progress for {data['foreign_key']}")
def handle_task_completed(data):
"""Handle task completed status"""
print(f"Task {data['task_id']} completed successfully")
print(f"Reviews URLs: {len(data.get('reviews_urls', []))} files")
# Store completed task data
task_data = {
'task_id': data['task_id'],
'foreign_key': data['foreign_key'],
'publisher_key': data['publisher_key'],
'profile_key': data['profile_key'],
'status': 'completed',
'task_status': data['task_status'],
'reviews_urls': data.get('reviews_urls', []),
'completed_at': datetime.now().isoformat()
}
# Store in database
store_completed_task(task_data)
# Send completion notification
send_notification(f"Task completed: Reviews available for {data['foreign_key']}")
# Optionally download and process reviews
if data.get('reviews_urls'):
download_and_process_reviews(data['reviews_urls'], data['task_id'])
def handle_task_failed(data):
"""Handle task failed status"""
print(f"Task {data['task_id']} failed with status {data['task_status']}")
# Log error details
log_error({
'task_id': data['task_id'],
'foreign_key': data['foreign_key'],
'publisher_key': data['publisher_key'],
'profile_key': data['profile_key'],
'task_status': data['task_status'],
'timestamp': datetime.now().isoformat()
})
# Send alert to administrators
send_alert(f"Task failed: {data['task_id']} (Status: {data['task_status']})")
# Helper functions (implement based on your needs)
def update_task_status(task_id, status, data):
"""Update task status in your database"""
print(f"Updating task {task_id} status to {status}")
# Your database logic here
def store_completed_task(task_data):
"""Store completed task data in your database"""
print('Storing completed task:', task_data)
# Your database logic here
def download_and_process_reviews(reviews_urls, task_id):
"""Download and process reviews from S3 URLs"""
print(f"Downloading reviews for task {task_id} from {len(reviews_urls)} URLs")
# Your download and processing logic here
def send_notification(message):
"""Send notification (email, Slack, etc.)"""
print('Sending notification:', message)
# Your notification logic here
def send_alert(message):
"""Send alert to administrators"""
print('Sending alert:', message)
# Your alerting logic here
def log_error(error_data):
"""Log error to your logging system"""
print('Logging error:', error_data)
# Your logging logic here
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
<?php
// Main webhook handler
function handleWebhook() {
// Get raw POST data
$payload = file_get_contents('php://input');
$data = json_decode($payload, true);
if (!$data) {
http_response_code(400);
echo json_encode(['error' => 'Invalid JSON']);
return;
}
$taskId = $data['task_id'] ?? '';
$publisherKey = $data['publisher_key'] ?? '';
$foreignKey = $data['foreign_key'] ?? '';
$taskStatus = $data['task_status'] ?? '';
$reviewsUrls = $data['reviews_urls'] ?? [];
error_log("Received webhook for task: $taskId");
error_log("Publisher: $publisherKey");
error_log("Foreign key: $foreignKey");
error_log("Task status: $taskStatus");
// Handle different task statuses
if ($taskStatus == 200) {
handleTaskCompleted($data);
} elseif ($taskStatus == 100) {
handleTaskQueued($data);
} elseif ($taskStatus == 101) {
handleTaskInProgress($data);
} elseif ($taskStatus >= 400) {
handleTaskFailed($data);
} else {
error_log("Unknown task status: $taskStatus");
}
// Return success response
http_response_code(200);
echo json_encode(['status' => 'success', 'message' => 'Webhook processed']);
}
function handleTaskQueued($data) {
error_log("Task {$data['task_id']} queued for processing");
error_log("Publisher: {$data['publisher_key']}, Foreign key: {$data['foreign_key']}");
// Update your database with queued status
updateTaskStatus($data['task_id'], 'queued', $data);
}
function handleTaskInProgress($data) {
error_log("Task {$data['task_id']} in progress for {$data['publisher_key']}");
// Update your database with in-progress status
updateTaskStatus($data['task_id'], 'in_progress', $data);
// Send notification to users
sendNotification("Review collection in progress for {$data['foreign_key']}");
}
function handleTaskCompleted($data) {
error_log("Task {$data['task_id']} completed successfully");
error_log("Reviews URLs: " . count($data['reviews_urls'] ?? []) . " files");
// Store completed task data
$taskData = [
'task_id' => $data['task_id'],
'foreign_key' => $data['foreign_key'],
'publisher_key' => $data['publisher_key'],
'profile_key' => $data['profile_key'],
'status' => 'completed',
'task_status' => $data['task_status'],
'reviews_urls' => $data['reviews_urls'] ?? [],
'completed_at' => date('c')
];
// Store in database
storeCompletedTask($taskData);
// Send completion notification
sendNotification("Task completed: Reviews available for {$data['foreign_key']}");
// Optionally download and process reviews
if (!empty($data['reviews_urls'])) {
downloadAndProcessReviews($data['reviews_urls'], $data['task_id']);
}
}
function handleTaskFailed($data) {
error_log("Task {$data['task_id']} failed with status {$data['task_status']}");
// Log error details
logError([
'task_id' => $data['task_id'],
'foreign_key' => $data['foreign_key'],
'publisher_key' => $data['publisher_key'],
'profile_key' => $data['profile_key'],
'task_status' => $data['task_status'],
'timestamp' => date('c')
]);
// Send alert to administrators
sendAlert("Task failed: {$data['task_id']} (Status: {$data['task_status']})");
}
// Helper functions (implement based on your needs)
function updateTaskStatus($taskId, $status, $data) {
error_log("Updating task $taskId status to $status");
// Your database logic here
}
function storeCompletedTask($taskData) {
error_log('Storing completed task: ' . json_encode($taskData));
// Your database logic here
}
function downloadAndProcessReviews($reviewsUrls, $taskId) {
error_log("Downloading reviews for task $taskId from " . count($reviewsUrls) . " URLs");
// Your download and processing logic here
}
function sendNotification($message) {
error_log("Sending notification: $message");
// Your notification logic here
}
function sendAlert($message) {
error_log("Sending alert: $message");
// Your alerting logic here
}
function logError($errorData) {
error_log('Logging error: ' . json_encode($errorData));
// Your logging logic here
}
// Handle the webhook
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
handleWebhook();
} else {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
?>
Webhook Payload Structure
The system sends webhooks with the following payload structure:
{
"task_id": "900e2ff4-2d25-4576-ab18-b9b8e73c0bd6",
"publisher_key": "maps.google.com",
"foreign_key": "TEST_1751004113518_i6yxcl",
"profile_key": "https://www.google.com/maps/place/@40.7094789,-74.0126167,886m/data=!3m2!1e3!5s0x89c2592f4977ef97:0xf78d57398ac93494!4m7!3m6!1s0x89c25a177d4bf5db:0x84e51f23e8c0a75c!8m2!3d40.7094789!4d-74.0100364!10e1!16s%2Fg%2F1thtf190!5m1!1e1?entry=ttu&g_ep=EgoyMDI1MDY",
"task_status": 200,
"reviews_urls": [
"https://prod-data-only-client.s3.amazonaws.com/Review_URLs/9f9d2b5a-085c-4df3-bcca-20928f086925/900e2ff4-2d25-4576-ab18-b9b8e73c0bd6_0.jl"
]
}
Payload Fields
Field | Type | Description |
---|---|---|
task_id | string | Unique identifier for the task |
publisher_key | string | Review platform (e.g., "maps.google.com") |
foreign_key | string | Your unique identifier for this request |
profile_key | string | URL to the business profile |
task_status | integer | Current status of the task |
reviews_urls | array | S3 URLs containing review data (present when completed) |
Task Status Values
Status | Description |
---|---|
100 | Task queued |
101 | Task in progress |
200 | Task completed successfully |
400+ | Task failed |
Webhook Examples by Status
Task Queued (Status: 100)
{
"task_id": "900e2ff4-2d25-4576-ab18-b9b8e73c0bd6",
"publisher_key": "maps.google.com",
"foreign_key": "TEST_1751004113518_i6yxcl",
"profile_key": "https://www.google.com/maps/place/@40.7094789,-74.0126167,886m/data=!3m2!1e3!5s0x89c2592f4977ef97:0xf78d57398ac93494!4m7!3m6!1s0x89c25a177d4bf5db:0x84e51f23e8c0a75c!8m2!3d40.7094789!4d-74.0100364!10e1!16s%2Fg%2F1thtf190!5m1!1e1?entry=ttu&g_ep=EgoyMDI1MDY",
"task_status": 100
}
Task In Progress (Status: 101)
{
"task_id": "900e2ff4-2d25-4576-ab18-b9b8e73c0bd6",
"publisher_key": "maps.google.com",
"foreign_key": "TEST_1751004113518_i6yxcl",
"profile_key": "https://www.google.com/maps/place/@40.7094789,-74.0126167,886m/data=!3m2!1e3!5s0x89c2592f4977ef97:0xf78d57398ac93494!4m7!3m6!1s0x89c25a177d4bf5db:0x84e51f23e8c0a75c!8m2!3d40.7094789!4d-74.0100364!10e1!16s%2Fg%2F1thtf190!5m1!1e1?entry=ttu&g_ep=EgoyMDI1MDY",
"task_status": 101
}
Task Completed (Status: 200)
{
"task_id": "900e2ff4-2d25-4576-ab18-b9b8e73c0bd6",
"publisher_key": "maps.google.com",
"foreign_key": "TEST_1751004113518_i6yxcl",
"profile_key": "https://www.google.com/maps/place/@40.7094789,-74.0126167,886m/data=!3m2!1e3!5s0x89c2592f4977ef97:0xf78d57398ac93494!4m7!3m6!1s0x89c25a177d4bf5db:0x84e51f23e8c0a75c!8m2!3d40.7094789!4d-74.0100364!10e1!16s%2Fg%2F1thtf190!5m1!1e1?entry=ttu&g_ep=EgoyMDI1MDY",
"task_status": 200,
"reviews_urls": [
"https://prod-data-only-client.s3.amazonaws.com/Review_URLs/9f9d2b5a-085c-4df3-bcca-20928f086925/900e2ff4-2d25-4576-ab18-b9b8e73c0bd6_0.jl"
]
}
Task Failed (Status: 400+)
{
"task_id": "900e2ff4-2d25-4576-ab18-b9b8e73c0bd6",
"publisher_key": "maps.google.com",
"foreign_key": "TEST_1751004113518_i6yxcl",
"profile_key": "https://www.google.com/maps/place/@40.7094789,-74.0126167,886m/data=!3m2!1e3!5s0x89c2592f4977ef97:0xf78d57398ac93494!4m7!3m6!1s0x89c25a177d4bf5db:0x84e51f23e8c0a75c!8m2!3d40.7094789!4d-74.0100364!10e1!16s%2Fg%2F1thtf190!5m1!1e1?entry=ttu&g_ep=EgoyMDI1MDY",
"task_status": 400
}
Testing Webhooks
Using ngrok for Local Testing
- Install ngrok:
npm install -g ngrok
- Start your local webhook server
- Expose your local server:
ngrok http 3000
- Configure webhook with ngrok URL:
https://abc123.ngrok.io/webhook
Webhook Testing Service
- JavaScript (Node.js)
- Python
- PHP
// Simple webhook testing server
const express = require('express');
const app = express();
app.use(express.json());
app.post('/webhook', (req, res) => {
console.log('Received webhook:');
console.log(JSON.stringify(req.body, null, 2));
console.log('Headers:', req.headers);
// Extract webhook data
const { task_id, publisher_key, foreign_key, task_status, reviews_urls } = req.body;
console.log(`Task ID: ${task_id}`);
console.log(`Publisher: ${publisher_key}`);
console.log(`Foreign Key: ${foreign_key}`);
console.log(`Status: ${task_status}`);
if (reviews_urls) {
console.log(`Reviews URLs: ${reviews_urls.length} files`);
}
res.status(200).json({ status: 'received' });
});
app.listen(3000, () => {
console.log('Webhook test server running on port 3000');
});
from flask import Flask, request, jsonify
import json
app = Flask(__name__)
@app.route('/webhook', methods=['POST'])
def webhook():
print('Received webhook:')
data = request.get_json()
print(json.dumps(data, indent=2))
print('Headers:', dict(request.headers))
# Extract webhook data
task_id = data.get('task_id')
publisher_key = data.get('publisher_key')
foreign_key = data.get('foreign_key')
task_status = data.get('task_status')
reviews_urls = data.get('reviews_urls')
print(f'Task ID: {task_id}')
print(f'Publisher: {publisher_key}')
print(f'Foreign Key: {foreign_key}')
print(f'Status: {task_status}')
if reviews_urls:
print(f'Reviews URLs: {len(reviews_urls)} files')
return jsonify({'status': 'received'}), 200
if __name__ == '__main__':
app.run(host='0.0.0.0', port=3000)
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$payload = file_get_contents('php://input');
$data = json_decode($payload, true);
echo "Received webhook:\n";
echo json_encode($data, JSON_PRETTY_PRINT) . "\n";
echo "Headers:\n";
foreach (getallheaders() as $name => $value) {
echo "$name: $value\n";
}
// Extract webhook data
$taskId = $data['task_id'] ?? '';
$publisherKey = $data['publisher_key'] ?? '';
$foreignKey = $data['foreign_key'] ?? '';
$taskStatus = $data['task_status'] ?? '';
$reviewsUrls = $data['reviews_urls'] ?? [];
echo "Task ID: $taskId\n";
echo "Publisher: $publisherKey\n";
echo "Foreign Key: $foreignKey\n";
echo "Status: $taskStatus\n";
if (!empty($reviewsUrls)) {
echo "Reviews URLs: " . count($reviewsUrls) . " files\n";
}
http_response_code(200);
echo json_encode(['status' => 'received']);
} else {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
?>
Webhook Security Best Practices
1. Use HTTPS
Always use HTTPS for webhook endpoints to ensure data is encrypted in transit.
2. Validate Input Data
- JavaScript (Node.js)
- Python
- PHP
function validateWebhookData(data) {
const required = ['task_id', 'publisher_key', 'foreign_key', 'task_status'];
for (const field of required) {
if (!data[field]) {
throw new Error(`Missing required field: ${field}`);
}
}
if (typeof data.task_status !== 'number') {
throw new Error('task_status must be a number');
}
return true;
}
app.post('/webhook', (req, res) => {
try {
validateWebhookData(req.body);
// Process webhook...
} catch (error) {
console.error('Validation error:', error);
return res.status(400).json({ error: error.message });
}
});
def validate_webhook_data(data):
required = ['task_id', 'publisher_key', 'foreign_key', 'task_status']
for field in required:
if field not in data:
raise ValueError(f'Missing required field: {field}')
if not isinstance(data['task_status'], int):
raise ValueError('task_status must be an integer')
return True
@app.route('/webhook', methods=['POST'])
def webhook():
try:
data = request.get_json()
validate_webhook_data(data)
# Process webhook...
except ValueError as e:
print(f'Validation error: {e}')
return jsonify({'error': str(e)}), 400
<?php
function validateWebhookData($data) {
$required = ['task_id', 'publisher_key', 'foreign_key', 'task_status'];
foreach ($required as $field) {
if (!isset($data[$field])) {
throw new InvalidArgumentException("Missing required field: $field");
}
}
if (!is_int($data['task_status'])) {
throw new InvalidArgumentException('task_status must be an integer');
}
return true;
}
function handleWebhook() {
try {
$payload = file_get_contents('php://input');
$data = json_decode($payload, true);
validateWebhookData($data);
// Process webhook...
} catch (InvalidArgumentException $e) {
http_response_code(400);
echo json_encode(['error' => $e->getMessage()]);
return;
}
}
?>
3. Handle Idempotency
- JavaScript (Node.js)
- Python
- PHP
const processedWebhooks = new Set();
app.post('/webhook', (req, res) => {
const webhookId = `${req.body.task_id}_${req.body.task_status}`;
if (processedWebhooks.has(webhookId)) {
console.log('Webhook already processed:', webhookId);
return res.status(200).json({ status: 'already_processed' });
}
processedWebhooks.add(webhookId);
// Process webhook...
res.status(200).json({ status: 'processed' });
});
processed_webhooks = set()
@app.route('/webhook', methods=['POST'])
def webhook():
data = request.get_json()
webhook_id = f"{data['task_id']}_{data['task_status']}"
if webhook_id in processed_webhooks:
print(f'Webhook already processed: {webhook_id}')
return jsonify({'status': 'already_processed'}), 200
processed_webhooks.add(webhook_id)
# Process webhook...
return jsonify({'status': 'processed'}), 200
<?php
$processedWebhooks = [];
function handleWebhook() {
global $processedWebhooks;
$payload = file_get_contents('php://input');
$data = json_decode($payload, true);
$webhookId = $data['task_id'] . '_' . $data['task_status'];
if (in_array($webhookId, $processedWebhooks)) {
error_log("Webhook already processed: $webhookId");
http_response_code(200);
echo json_encode(['status' => 'already_processed']);
return;
}
$processedWebhooks[] = $webhookId;
// Process webhook...
http_response_code(200);
echo json_encode(['status' => 'processed']);
}
?>
4. Implement Retry Logic
- JavaScript (Node.js)
- Python
- PHP
app.post('/webhook', async (req, res) => {
try {
await processWebhook(req.body);
res.status(200).json({ status: 'success' });
} catch (error) {
console.error('Webhook processing failed:', error);
// Return 5xx status to trigger retry
res.status(500).json({ error: 'Processing failed' });
}
});
@app.route('/webhook', methods=['POST'])
def webhook():
try:
process_webhook(request.get_json())
return jsonify({'status': 'success'}), 200
except Exception as e:
print(f'Webhook processing failed: {e}')
# Return 5xx status to trigger retry
return jsonify({'error': 'Processing failed'}), 500
<?php
function handleWebhook() {
try {
$payload = file_get_contents('php://input');
$data = json_decode($payload, true);
processWebhook($data);
http_response_code(200);
echo json_encode(['status' => 'success']);
} catch (Exception $e) {
error_log('Webhook processing failed: ' . $e->getMessage());
// Return 5xx status to trigger retry
http_response_code(500);
echo json_encode(['error' => 'Processing failed']);
}
}
?>
Integration Examples
Database Storage Example
- JavaScript (MongoDB)
- Python (SQLite)
- PHP (MySQL)
// Using MongoDB
const mongoose = require('mongoose');
const TaskSchema = new mongoose.Schema({
taskId: { type: String, required: true, unique: true },
foreignKey: { type: String, required: true },
publisherKey: { type: String, required: true },
status: { type: String, required: true },
taskStatus: { type: Number, required: true },
reviewsCount: { type: Number, default: 0 },
reviewsUrls: [String],
createdAt: { type: Date, default: Date.now },
updatedAt: { type: Date, default: Date.now }
});
const Task = mongoose.model('Task', TaskSchema);
async function storeCompletedTask(taskData) {
await Task.findOneAndUpdate(
{ taskId: taskData.taskId },
{
...taskData,
updatedAt: new Date()
},
{ upsert: true }
);
}
import sqlite3
from datetime import datetime
def init_database():
conn = sqlite3.connect('webhooks.db')
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
task_id TEXT UNIQUE NOT NULL,
foreign_key TEXT NOT NULL,
publisher_key TEXT NOT NULL,
status TEXT NOT NULL,
task_status INTEGER NOT NULL,
reviews_count INTEGER DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
conn.commit()
conn.close()
def store_completed_task(task_data):
conn = sqlite3.connect('webhooks.db')
cursor = conn.cursor()
cursor.execute('''
INSERT OR REPLACE INTO tasks
(task_id, foreign_key, publisher_key, status, task_status, reviews_count, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?)
''', (
task_data['task_id'],
task_data['foreign_key'],
task_data['publisher_key'],
task_data['status'],
task_data['task_status'],
task_data.get('reviews_count', 0),
datetime.now()
))
conn.commit()
conn.close()
<?php
function initDatabase() {
$pdo = new PDO('mysql:host=localhost;dbname=webhooks', $username, $password);
$pdo->exec("
CREATE TABLE IF NOT EXISTS tasks (
id INT AUTO_INCREMENT PRIMARY KEY,
task_id VARCHAR(255) UNIQUE NOT NULL,
foreign_key VARCHAR(255) NOT NULL,
publisher_key VARCHAR(255) NOT NULL,
status VARCHAR(50) NOT NULL,
task_status INT NOT NULL,
reviews_count INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)
");
return $pdo;
}
function storeCompletedTask($taskData) {
$pdo = initDatabase();
$stmt = $pdo->prepare("
INSERT INTO tasks (task_id, foreign_key, publisher_key, status, task_status, reviews_count)
VALUES (?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
status = VALUES(status),
task_status = VALUES(task_status),
reviews_count = VALUES(reviews_count),
updated_at = CURRENT_TIMESTAMP
");
$stmt->execute([
$taskData['task_id'],
$taskData['foreign_key'],
$taskData['publisher_key'],
$taskData['status'],
$taskData['task_status'],
$taskData['reviews_count'] ?? 0
]);
}
?>
Email Notification Example
- JavaScript (Nodemailer)
- Python (smtplib)
- PHP (PHPMailer)
const nodemailer = require('nodemailer');
const transporter = nodemailer.createTransporter({
service: 'gmail',
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS
}
});
async function sendNotification(message) {
const mailOptions = {
from: process.env.EMAIL_USER,
to: 'admin@yourcompany.com',
subject: 'ReviewData API Notification',
text: message
};
try {
await transporter.sendMail(mailOptions);
console.log('Notification sent:', message);
} catch (error) {
console.error('Failed to send notification:', error);
}
}
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import os
def send_notification(message):
smtp_server = "smtp.gmail.com"
smtp_port = 587
sender_email = os.environ.get('EMAIL_USER')
sender_password = os.environ.get('EMAIL_PASS')
recipient_email = 'admin@yourcompany.com'
msg = MIMEMultipart()
msg['From'] = sender_email
msg['To'] = recipient_email
msg['Subject'] = 'ReviewData API Notification'
msg.attach(MIMEText(message, 'plain'))
try:
server = smtplib.SMTP(smtp_server, smtp_port)
server.starttls()
server.login(sender_email, sender_password)
server.send_message(msg)
server.quit()
print(f'Notification sent: {message}')
except Exception as e:
print(f'Failed to send notification: {e}')
<?php
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
function sendNotification($message) {
$mail = new PHPMailer(true);
try {
// Server settings
$mail->isSMTP();
$mail->Host = 'smtp.gmail.com';
$mail->SMTPAuth = true;
$mail->Username = $_ENV['EMAIL_USER'];
$mail->Password = $_ENV['EMAIL_PASS'];
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
$mail->Port = 587;
// Recipients
$mail->setFrom($_ENV['EMAIL_USER'], 'ReviewData API');
$mail->addAddress('admin@yourcompany.com');
// Content
$mail->isHTML(false);
$mail->Subject = 'ReviewData API Notification';
$mail->Body = $message;
$mail->send();
echo "Notification sent: $message\n";
} catch (Exception $e) {
echo "Failed to send notification: {$mail->ErrorInfo}\n";
}
}
?>
Slack Integration Example
- JavaScript (Axios)
- Python (Requests)
- PHP (cURL)
const axios = require('axios');
async function sendSlackAlert(message) {
const slackWebhookUrl = process.env.SLACK_WEBHOOK_URL;
if (!slackWebhookUrl) {
console.warn('Slack webhook URL not configured');
return;
}
try {
await axios.post(slackWebhookUrl, {
text: message,
username: 'ReviewData Bot',
icon_emoji: ':robot_face:'
});
console.log('Slack alert sent:', message);
} catch (error) {
console.error('Failed to send Slack alert:', error);
}
}
import requests
import os
def send_slack_alert(message):
slack_webhook_url = os.environ.get('SLACK_WEBHOOK_URL')
if not slack_webhook_url:
print('Slack webhook URL not configured')
return
payload = {
'text': message,
'username': 'ReviewData Bot',
'icon_emoji': ':robot_face:'
}
try:
response = requests.post(slack_webhook_url, json=payload)
response.raise_for_status()
print(f'Slack alert sent: {message}')
except requests.exceptions.RequestException as e:
print(f'Failed to send Slack alert: {e}')
<?php
function sendSlackAlert($message) {
$slackWebhookUrl = $_ENV['SLACK_WEBHOOK_URL'] ?? null;
if (!$slackWebhookUrl) {
error_log('Slack webhook URL not configured');
return;
}
$payload = [
'text' => $message,
'username' => 'ReviewData Bot',
'icon_emoji' => ':robot_face:'
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $slackWebhookUrl);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode == 200) {
error_log("Slack alert sent: $message");
} else {
error_log("Failed to send Slack alert: HTTP $httpCode");
}
}
?>
Error Handling
Common Webhook Errors
- Invalid JSON: Malformed webhook payload
- Missing Required Fields: Webhook missing expected fields
- Database Connection Error: Database unavailable
- External Service Error: Email/Slack service unavailable
Error Response Format
- JavaScript (Express)
- Python (Flask)
- PHP
app.post('/webhook', (req, res) => {
try {
// Process webhook
res.status(200).json({ status: 'success' });
} catch (error) {
console.error('Webhook error:', error);
// Return appropriate error status
if (error.name === 'ValidationError') {
res.status(400).json({ error: 'Invalid webhook data' });
} else if (error.name === 'DatabaseError') {
res.status(500).json({ error: 'Database unavailable' });
} else {
res.status(500).json({ error: 'Internal server error' });
}
}
});
@app.route('/webhook', methods=['POST'])
def webhook():
try:
# Process webhook
return jsonify({'status': 'success'}), 200
except ValueError as e:
print(f'Validation error: {e}')
return jsonify({'error': 'Invalid webhook data'}), 400
except ConnectionError as e:
print(f'Database error: {e}')
return jsonify({'error': 'Database unavailable'}), 500
except Exception as e:
print(f'Webhook error: {e}')
return jsonify({'error': 'Internal server error'}), 500
<?php
function handleWebhook() {
try {
// Process webhook
http_response_code(200);
echo json_encode(['status' => 'success']);
} catch (InvalidArgumentException $e) {
error_log('Validation error: ' . $e->getMessage());
http_response_code(400);
echo json_encode(['error' => 'Invalid webhook data']);
} catch (PDOException $e) {
error_log('Database error: ' . $e->getMessage());
http_response_code(500);
echo json_encode(['error' => 'Database unavailable']);
} catch (Exception $e) {
error_log('Webhook error: ' . $e->getMessage());
http_response_code(500);
echo json_encode(['error' => 'Internal server error']);
}
}
?>
Monitoring and Logging
Webhook Monitoring
- JavaScript (Metrics)
- Python (Logging)
- PHP (Error Logging)
// Track webhook metrics
const webhookMetrics = {
received: 0,
processed: 0,
failed: 0,
processingTime: []
};
app.post('/webhook', async (req, res) => {
const startTime = Date.now();
webhookMetrics.received++;
try {
await processWebhook(req.body);
webhookMetrics.processed++;
res.status(200).json({ status: 'success' });
} catch (error) {
webhookMetrics.failed++;
console.error('Webhook failed:', error);
res.status(500).json({ error: 'Processing failed' });
} finally {
const processingTime = Date.now() - startTime;
webhookMetrics.processingTime.push(processingTime);
// Log metrics periodically
if (webhookMetrics.received % 100 === 0) {
console.log('Webhook metrics:', webhookMetrics);
}
}
});
import time
import logging
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
webhook_metrics = {
'received': 0,
'processed': 0,
'failed': 0,
'processing_time': []
}
@app.route('/webhook', methods=['POST'])
def webhook():
start_time = time.time()
webhook_metrics['received'] += 1
try:
process_webhook(request.get_json())
webhook_metrics['processed'] += 1
return jsonify({'status': 'success'}), 200
except Exception as e:
webhook_metrics['failed'] += 1
logger.error(f'Webhook failed: {e}')
return jsonify({'error': 'Processing failed'}), 500
finally:
processing_time = time.time() - start_time
webhook_metrics['processing_time'].append(processing_time)
# Log metrics periodically
if webhook_metrics['received'] % 100 == 0:
logger.info(f'Webhook metrics: {webhook_metrics}')
<?php
$webhookMetrics = [
'received' => 0,
'processed' => 0,
'failed' => 0,
'processing_time' => []
];
function handleWebhook() {
global $webhookMetrics;
$startTime = microtime(true);
$webhookMetrics['received']++;
try {
processWebhook(json_decode(file_get_contents('php://input'), true));
$webhookMetrics['processed']++;
http_response_code(200);
echo json_encode(['status' => 'success']);
} catch (Exception $e) {
$webhookMetrics['failed']++;
error_log('Webhook failed: ' . $e->getMessage());
http_response_code(500);
echo json_encode(['error' => 'Processing failed']);
} finally {
$processingTime = microtime(true) - $startTime;
$webhookMetrics['processing_time'][] = $processingTime;
// Log metrics periodically
if ($webhookMetrics['received'] % 100 === 0) {
error_log('Webhook metrics: ' . json_encode($webhookMetrics));
}
}
}
?>
Best Practices Summary
- Use HTTPS: Always use HTTPS for webhook endpoints
- Verify Signatures: Implement signature verification for security
- Handle Idempotency: Process each webhook only once
- Implement Retry Logic: Handle temporary failures gracefully
- Monitor Performance: Track webhook processing metrics
- Log Everything: Log webhook events for debugging
- Handle Errors Gracefully: Return appropriate HTTP status codes
- Use Queues: For high-volume webhooks, use message queues
- Test Thoroughly: Test webhook handling with various scenarios
- Keep Secrets Secure: Store webhook secrets securely
Need Help?
If you encounter issues with webhooks:
- Test Configuration: Use the interactive playground to test webhook setup
- Check Logs: Review your server logs for webhook processing errors
- Verify Endpoint: Ensure your webhook endpoint is accessible via HTTPS
- Test Locally: Use ngrok to test webhooks locally during development
- Contact Support: Email techsupport@shoutaboutus.com for assistance
Next Steps
- Learn about Request Examples for submitting review requests
- Explore Response Examples for handling API responses
- Check the API Playground for interactive testing