Back to API Docs

Webhook Documentation

Real-time notifications for Swappable Images

Overview

Webhooks allow you to receive real-time HTTP POST notifications when events happen to your swappable images. Instead of polling our API, we'll push updates directly to your server.

Real-time

Get notified instantly when images are swapped

Secure

HMAC signatures verify authenticity

Reliable

Automatic retries on failure

Setup Guide

1

Create an endpoint on your server

Your endpoint must accept POST requests and return a 2xx status code within 10 seconds.

javascript
// Express.js example
app.post('/webhooks/foldr', (req, res) => {
  const event = req.body;
  
  console.log('Received webhook:', event.event);
  console.log('Image ID:', event.image_id);
  
  // Process the webhook...
  
  res.status(200).json({ received: true });
});
2

Configure your webhook URL

Go to your swappable image settings → Webhooks tab → Enter your endpoint URL. A signing secret will be automatically generated.

3

Select events to receive

Choose which events trigger webhooks. You can enable/disable events at any time.

4

Test your webhook

Click "Send Test" to send a test payload and verify your endpoint is working.

Webhook Events

EventDescription
image.swappedTriggered when a new image version is uploaded and becomes the current version
image.revertedTriggered when the image is reverted to a previous version
schedule.executedTriggered when a scheduled swap is automatically executed
testTest event sent when you click "Send Test" in the dashboard

Payload Format

All webhook payloads follow the same structure:

json
{
  "event": "image.swapped",
  "image_id": "550e8400-e29b-41d4-a716-446655440000",
  "version_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
  "version_number": 5,
  "timestamp": "2025-12-05T14:30:00.000Z",
  "data": {
    "original_name": "banner-v5.png",
    "file_size": 245678,
    "mime_type": "image/png"
  }
}

Revert Event Example

json
{
  "event": "image.reverted",
  "image_id": "550e8400-e29b-41d4-a716-446655440000",
  "version_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
  "version_number": 3,
  "timestamp": "2025-12-05T14:30:00.000Z",
  "data": {
    "from_version": 5,
    "to_version": 3
  }
}

HTTP Headers

Each webhook request includes these headers:

HeaderDescription
Content-TypeAlways application/json
User-AgentFoldr-Webhooks/1.0
X-Webhook-EventThe event type (e.g., swap)
X-Webhook-DeliveryUnique ID for this delivery (UUID)
X-Webhook-SignatureHMAC signature for verification

Signature Verification

Always verify webhook signatures to ensure requests are from Foldr. The signature is in theX-Webhook-Signature header.

Security Warning

Never process webhooks without verifying the signature. This protects you from spoofed requests.

Signature Format

text
X-Webhook-Signature: t=1701782345,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd

The signature contains a timestamp (t=) and the HMAC-SHA256 signature (v1=).

Node.js Verification Example

javascript
const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
  // Parse signature header
  const parts = signature.split(',');
  const timestamp = parts.find(p => p.startsWith('t='))?.slice(2);
  const expectedSig = parts.find(p => p.startsWith('v1='))?.slice(3);
  
  if (!timestamp || !expectedSig) {
    return false;
  }
  
  // Check timestamp is recent (within 5 minutes)
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - parseInt(timestamp)) > 300) {
    return false; // Replay attack protection
  }
  
  // Compute expected signature
  const signedPayload = `${timestamp}.${payload}`;
  const computedSig = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');
  
  // Use timing-safe comparison
  return crypto.timingSafeEqual(
    Buffer.from(expectedSig),
    Buffer.from(computedSig)
  );
}

// Express middleware
app.post('/webhooks/foldr', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  const payload = req.body.toString();
  
  if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }
  
  const event = JSON.parse(payload);
  // Process verified webhook...
  
  res.status(200).json({ received: true });
});

Python Verification Example

python
import hmac
import hashlib
import time

def verify_webhook_signature(payload: str, signature: str, secret: str) -> bool:
    # Parse signature
    parts = dict(p.split('=') for p in signature.split(','))
    timestamp = parts.get('t')
    expected_sig = parts.get('v1')
    
    if not timestamp or not expected_sig:
        return False
    
    # Check timestamp (within 5 minutes)
    if abs(time.time() - int(timestamp)) > 300:
        return False
    
    # Compute signature
    signed_payload = f"{timestamp}.{payload}"
    computed_sig = hmac.new(
        secret.encode(),
        signed_payload.encode(),
        hashlib.sha256
    ).hexdigest()
    
    return hmac.compare_digest(expected_sig, computed_sig)

# Flask example
@app.route('/webhooks/foldr', methods=['POST'])
def handle_webhook():
    signature = request.headers.get('X-Webhook-Signature')
    payload = request.get_data(as_text=True)
    
    if not verify_webhook_signature(payload, signature, WEBHOOK_SECRET):
        return jsonify({'error': 'Invalid signature'}), 401
    
    event = request.get_json()
    # Process verified webhook...
    
    return jsonify({'received': True}), 200

Retry Behavior

If your endpoint returns a non-2xx status code or times out, we'll retry the webhook:

AttemptDelay
1st retry1 minute after initial failure
2nd retry5 minutes after 1st retry
3rd retry (final)15 minutes after 2nd retry

Timeout

Your endpoint must respond within 10 seconds. If processing takes longer, acknowledge receipt immediately and process asynchronously.

Best Practices

Return 2xx quickly

Acknowledge receipt immediately, then process the webhook asynchronously if needed.

Handle duplicates

Use the X-Webhook-Delivery header to deduplicate in case of retries.

Use HTTPS

Always use HTTPS endpoints in production to protect webhook payloads in transit.

Log everything

Keep logs of received webhooks for debugging. You can also view delivery logs in your dashboard.

Testing Webhooks

For local development, use a tunneling service to expose your local server:

bash
# Using ngrok
ngrok http 3000

# Your webhook URL will be something like:
# https://abc123.ngrok.io/webhooks/foldr

Popular options include ngrok, localtunnel, or webhook.site for quick testing.

Ready to get started?

Configure webhooks for your swappable images in just a few clicks.

Go to Dashboard