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.
Create a webhook endpoint
Set up an HTTPS endpoint on your server to receive webhook payloads.
Register the webhook
Add your endpoint URL and select which events to subscribe to.
Verify signatures
Implement signature verification to ensure payloads are authentic.
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 createdqr.updatedA QR code was updatedqr.deletedA QR code was deletedqr.scannedA QR code was scannedqr.archivedA QR code was archivedqr.restoredA QR code was restored from archiveSubscription Events
subscription.createdA new subscription was createdsubscription.updatedA subscription was updatedsubscription.cancelledA subscription was cancelledsubscription.payment_succeededA payment was successfulsubscription.payment_failedA payment failedTeam Events
member.joinedA member joined the organizationmember.leftA member left the organizationmember.role_changedA member's role was changedWebhook Payload
All webhook payloads follow a consistent structure with event metadata and data.
{
"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
}
}
}{
"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.
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');
});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', 200Best 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:
| Attempt | Delay |
|---|---|
| 1st retry | 1 minute |
| 2nd retry | 5 minutes |
| 3rd retry | 30 minutes |
| 4th retry | 2 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: