API Documentation
Webhooks

Webhooks

Receive real-time notifications when events occur in your organization. Webhooks allow you to build reactive integrations that respond immediately to QR code scans and other events.

Real-time

Get notified instantly when events occur, no polling required

Secure

HMAC signatures verify that webhooks come from iloveQR

Reliable

Automatic retries with exponential backoff for failed deliveries

Setting Up Webhooks

Configure webhooks through the dashboard or API to start receiving events.

1

Create a webhook endpoint

Set up an HTTPS endpoint on your server to receive webhook payloads.

2

Register the webhook

Add your endpoint URL and select which events to subscribe to.

3

Verify signatures

Implement signature verification to ensure payloads are authentic.

Register Webhook
curl -X POST \
  "https://i-love-qr-production-645dbff8d2fe.herokuapp.com/api/v1/organizations/org_abc123/webhooks" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-server.com/webhooks/iloveqr",
    "events": ["qr.scanned", "qr.created", "qr.updated"],
    "secret": "your_webhook_secret"
  }'

Webhook Events

Subscribe to the events you need. Each event type has a specific payload structure.

QR Code Events

qr.createdA new QR code was created
qr.updatedA QR code was updated
qr.deletedA QR code was deleted
qr.scannedA QR code was scanned
qr.archivedA QR code was archived
qr.restoredA QR code was restored from archive

Subscription Events

subscription.createdA new subscription was created
subscription.updatedA subscription was updated
subscription.cancelledA subscription was cancelled
subscription.payment_succeededA payment was successful
subscription.payment_failedA payment failed

Team Events

member.joinedA member joined the organization
member.leftA member left the organization
member.role_changedA member's role was changed

Webhook Payload

All webhook payloads follow a consistent structure with event metadata and data.

Example: qr.scanned Event
{
  "id": "evt_1a2b3c4d5e6f",
  "event": "qr.scanned",
  "createdAt": "2024-01-15T14:30:00Z",
  "organizationId": "org_abc123",
  "data": {
    "qrCodeId": "qr_xyz789",
    "qrCodeName": "Product Landing Page",
    "shortCode": "xyz789",
    "destinationUrl": "https://example.com/product",
    "scan": {
      "id": "scan_123abc",
      "timestamp": "2024-01-15T14:30:00Z",
      "location": {
        "country": "US",
        "city": "New York",
        "latitude": 40.7128,
        "longitude": -74.0060
      },
      "device": {
        "type": "mobile",
        "os": "iOS",
        "browser": "Safari"
      },
      "isUnique": true
    }
  }
}
Example: qr.created Event
{
  "id": "evt_7g8h9i0j1k2l",
  "event": "qr.created",
  "createdAt": "2024-01-15T10:00:00Z",
  "organizationId": "org_abc123",
  "data": {
    "qrCode": {
      "id": "qr_newcode",
      "type": "URL",
      "name": "New Campaign QR",
      "shortUrl": "https://ilqr.co/newcode",
      "destinationUrl": "https://example.com/campaign",
      "createdBy": "user_abc123"
    }
  }
}

Verifying Webhooks

Always verify webhook signatures to ensure the payload came from iloveQR and wasn't tampered with in transit.

Signature Header

Each webhook request includes an X-Webhook-Signature header containing an HMAC-SHA256 signature of the request body.

Node.js Verification Example
const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

// In your webhook handler
app.post('/webhooks/iloveqr', (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  const payload = JSON.stringify(req.body);

  if (!verifyWebhookSignature(payload, signature, WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }

  // Process the webhook
  const event = req.body;
  console.log('Received event:', event.event);

  res.status(200).send('OK');
});
Python Verification Example
import hmac
import hashlib

def verify_webhook_signature(payload: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode(),
        payload,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, expected)

# In your Flask handler
@app.route('/webhooks/iloveqr', methods=['POST'])
def handle_webhook():
    signature = request.headers.get('X-Webhook-Signature')
    payload = request.get_data()

    if not verify_webhook_signature(payload, signature, WEBHOOK_SECRET):
        return 'Invalid signature', 401

    event = request.get_json()
    print(f"Received event: {event['event']}")

    return 'OK', 200

Best Practices

Respond quickly

Return a 2xx response within 5 seconds. Process webhooks asynchronously if your handling takes longer.

Handle duplicates

Store the event ID and check for duplicates. Webhooks may be delivered more than once in rare cases.

Use HTTPS

Always use HTTPS endpoints. We don't deliver webhooks to HTTP URLs.

Monitor failures

Set up monitoring for webhook delivery failures. Check the webhook logs in the dashboard for debugging.

Retry Policy

If your endpoint returns an error or doesn't respond, we automatically retry delivery with exponential backoff:

AttemptDelay
1st retry1 minute
2nd retry5 minutes
3rd retry30 minutes
4th retry2 hours
5th retry (final)24 hours

Note: After 5 failed attempts, the webhook is marked as failed and we stop retrying. You'll receive an email notification about the failure.

Next Steps

Ready to get started? Explore more resources: