Webhook Integration Guide
Learn how to integrate Fuuffy webhooks to receive real-time order updates.
Overview
Webhooks allow you to receive automatic notifications when order events occur, eliminating the need to poll the API for updates.
Benefits
- Real-time updates - Get notified instantly when events occur
- Reduced API calls - No need to poll for status changes
- Better user experience - Update customers immediately
- Automated workflows - Trigger actions based on events
Setup Process
1. Create a Webhook Endpoint
Create a PHP script to receive webhook notifications:
<?php
// webhook-handler.php
// Log incoming requests for debugging
error_log("Webhook received: " . file_get_contents('php://input'));
// Get the raw POST data
$payload = file_get_contents('php://input');
$event = json_decode($payload, true);
// Verify the webhook signature
$signature = $_SERVER['HTTP_X_FUUFFY_SIGNATURE'] ?? '';
$secret = getenv('FUUFFY_WEBHOOK_SECRET');
if (!verifySignature($payload, $signature, $secret)) {
http_response_code(401);
exit(json_encode(['error' => 'Invalid signature']));
}
// Process the event
processEvent($event);
// Always return 200 OK quickly
http_response_code(200);
echo json_encode(['received' => true]);
function verifySignature($payload, $signature, $secret) {
$expected = hash_hmac('sha256', $payload, $secret);
return hash_equals($expected, $signature);
}
function processEvent($event) {
// Queue the event for async processing
// or process it directly if it's quick
switch ($event['event']) {
case 'order.delivered':
handleDelivered($event['data']);
break;
case 'order.in_transit':
handleInTransit($event['data']);
break;
case 'order.exception':
handleException($event['data']);
break;
default:
error_log("Unknown event: {$event['event']}");
}
}
function handleDelivered($data) {
// Update database
// Send customer notification
// Trigger post-delivery workflow
error_log("Order {$data['tracking_number']} delivered");
}
function handleInTransit($data) {
// Update tracking status
error_log("Order {$data['tracking_number']} in transit");
}
function handleException($data) {
// Alert customer service team
// Send notification to customer
error_log("Exception for {$data['tracking_number']}");
}
?>
2. Make Your Endpoint Publicly Accessible
Your webhook endpoint must be:
- Publicly accessible via HTTPS
- Fast - Respond within 5 seconds
- Reliable - Return 200 OK status
For local development, use ngrok:
ngrok http 8000
3. Register Your Webhook
Contact support@fuuffy.com with:
- Your webhook URL
- Events you want to subscribe to
- Your webhook secret (for signature verification)
Event Types
| Event | Description | When It Fires |
|---|---|---|
order.created | Order created | When a new order is created |
order.picked_up | Package picked up | When carrier picks up the package |
order.in_transit | In transit | When package moves between facilities |
order.out_for_delivery | Out for delivery | When package is out for delivery |
order.delivered | Delivered | When package is delivered |
order.failed_delivery | Delivery failed | When delivery attempt fails |
order.exception | Exception occurred | When an exception occurs |
order.cancelled | Cancelled | When order is cancelled |
Webhook Payload Structure
{
"event": "order.delivered",
"timestamp": "2024-01-18T15:30:00Z",
"data": {
"order_id": "order_1a2b3c4d5e6f",
"tracking_number": "FUF123456789",
"status": "delivered",
"location": {
"city": "Los Angeles",
"state": "CA",
"country": "US"
},
"description": "Package delivered to recipient",
"delivered_to": "Jane Smith",
"signature_required": false,
"proof_of_delivery_url": "${API_URL}/v1/orders/order_1a2b3c4d5e6f/pod"
}
}
Security Best Practices
1. Verify Signatures
Always verify the X-Fuuffy-Signature header:
<?php
function verifyWebhookSignature($payload, $signature, $secret) {
$expected_signature = hash_hmac('sha256', $payload, $secret);
if (!hash_equals($expected_signature, $signature)) {
error_log("Invalid webhook signature");
return false;
}
return true;
}
?>
2. Use HTTPS Only
Never use HTTP for webhook endpoints. HTTPS is required.
3. Validate Event Data
Always validate the event data before processing:
<?php
function validateEvent($event) {
if (!isset($event['event']) || !isset($event['data'])) {
return false;
}
if (!isset($event['data']['order_id'])) {
return false;
}
return true;
}
?>
4. Handle Duplicates
Events may be sent multiple times. Use idempotency:
<?php
function processEventIdempotent($event) {
$event_id = $event['data']['order_id'] . '_' . $event['timestamp'];
// Check if already processed
if (isEventProcessed($event_id)) {
error_log("Event already processed: {$event_id}");
return;
}
// Process event
processEvent($event);
// Mark as processed
markEventProcessed($event_id);
}
?>
Testing Webhooks
Local Testing with ngrok
# Start your local server
php -S localhost:8000
# In another terminal, start ngrok
ngrok http 8000
# Use the ngrok URL for testing
# Example: https://abc123.ngrok.io/webhook-handler.php
Manual Testing
Send a test webhook using cURL:
curl -X POST http://localhost:8000/webhook-handler.php \
-H "Content-Type: application/json" \
-H "X-Fuuffy-Signature: test_signature" \
-d '{
"event": "order.delivered",
"timestamp": "2024-01-18T15:30:00Z",
"data": {
"order_id": "order_test",
"tracking_number": "TEST123",
"status": "delivered"
}
}'
Troubleshooting
Webhook Not Receiving Events
- Check that your endpoint is publicly accessible
- Verify HTTPS is configured correctly
- Check firewall settings
- Review server logs for errors
Signature Verification Failing
- Ensure you're using the correct webhook secret
- Verify you're hashing the raw request body
- Check for whitespace or encoding issues
Timeouts
- Return 200 OK immediately
- Process events asynchronously
- Optimize database queries
- Use queues for heavy processing