Skip to content

API Webhooks and Automation

Integrate Alva Digital Downloads with external systems using webhooks and API automation for powerful workflows.

What are webhooks:

HTTP callbacks triggered by events
Real-time notifications to your server
Push-based (no polling needed)
Enable automation and integrations

Use cases:

• Send data to CRM (Salesforce, HubSpot)
• Trigger email campaigns (Mailchimp)
• Update analytics (Google Analytics)
• Sync with accounting (QuickBooks)
• Custom business logic
• Third-party integrations

Configure webhook:

Settings → Advanced → Webhooks → Add Webhook
Endpoint URL: https://yourapp.com/webhooks/alva
Method: POST
Format: JSON
Authentication: HMAC-SHA256 signature
Timeout: 10 seconds
Retry: 3 attempts (exponential backoff)

Available events:

Order events:

☑ order.created - New digital order placed
☑ order.paid - Payment confirmed
☑ order.approved - Fraud check passed
☑ order.rejected - Fraud check failed
☐ order.fulfilled - Files delivered
☐ order.refunded - Order refunded

Download events:

☑ download.created - Download link generated
☑ download.accessed - Customer downloaded file
☐ download.limit_reached - Max downloads hit
☐ download.expired - Link expired
☐ download.revoked - Access revoked

File events:

☐ file.uploaded - New file added
☐ file.updated - File modified
☐ file.deleted - File removed
☐ file.mapped - File mapped to product

Customer events:

☐ customer.first_purchase - First digital purchase
☐ customer.repeat_purchase - Return customer

Example payload:

{
"event": "order.created",
"timestamp": "2024-01-15T15:30:00Z",
"shop": "yourshop.myshopify.com",
"api_version": "2024-01",
"data": {
"order": {
"id": "1045",
"number": "1045",
"created_at": "2024-01-15T15:30:00Z",
"total": "49.99",
"currency": "USD",
"financial_status": "paid",
"customer": {
"id": "12345",
"email": "customer@example.com",
"first_name": "John",
"last_name": "Smith",
"phone": "+1-555-123-4567"
},
"line_items": [
{
"id": "67890",
"product_id": "prod_123",
"title": "Complete Course Bundle",
"quantity": 1,
"price": "49.99",
"files": [
{
"id": "file_abc123",
"filename": "Chapter-1.pdf",
"size": 2500000,
"download_url": "https://..."
}
]
}
],
"download_link": "https://yourshop.com/download?key=...",
"expiry_date": "2024-03-15T15:30:00Z",
"download_limit": 5
}
},
"signature": "abc123def456..."
}

Example payload:

{
"event": "download.accessed",
"timestamp": "2024-01-15T16:00:00Z",
"shop": "yourshop.myshopify.com",
"data": {
"download": {
"id": "download_xyz789",
"token": "abc123def456",
"file": {
"id": "file_abc123",
"filename": "Chapter-1.pdf",
"size": 2500000
},
"customer": {
"email": "customer@example.com",
"name": "John Smith"
},
"order_number": "1045",
"download_count": 1,
"download_limit": 5,
"downloads_remaining": 4,
"ip_address": "192.168.1.100",
"user_agent": "Mozilla/5.0...",
"location": {
"country": "US",
"region": "CA",
"city": "San Francisco"
}
}
}
}

HMAC signature verification:

Node.js example:

const crypto = require('crypto');
function verifyWebhook(payload, signature, secret) {
const hmac = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
// Use timing-safe comparison
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(hmac)
);
}
// Express.js route
app.post('/webhooks/alva', (req, res) => {
const signature = req.headers['x-alva-hmac-sha256'];
const secret = process.env.WEBHOOK_SECRET;
if (!verifyWebhook(req.body, signature, secret)) {
return res.status(401).send('Invalid signature');
}
// Process webhook
const event = req.body.event;
const data = req.body.data;
// Handle event...
res.status(200).send('OK');
});

Python example:

import hmac
import hashlib
import json
def verify_webhook(payload, signature, secret):
computed = hmac.new(
secret.encode(),
json.dumps(payload).encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, computed)
# Flask route
@app.route('/webhooks/alva', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-Alva-HMAC-SHA256')
secret = os.environ['WEBHOOK_SECRET']
if not verify_webhook(request.json, signature, secret):
return 'Invalid signature', 401
event = request.json['event']
data = request.json['data']
# Handle event...
return 'OK', 200

Restrict webhook sources:

Settings → Webhooks → Security → IP Whitelist
Add Alva webhook IPs:
• 192.0.2.1
• 192.0.2.2
• 192.0.2.3
Firewall: Allow only these IPs
Result: Additional security layer

Event processing example:

CRM integration:

async function handleOrderCreated(data) {
const order = data.order;
const customer = order.customer;
// Add customer to CRM
await addToCRM({
email: customer.email,
firstName: customer.first_name,
lastName: customer.last_name,
tags: ['digital-customer'],
customFields: {
lastPurchase: order.created_at,
totalSpent: order.total,
productPurchased: order.line_items[0].title
}
});
// Trigger email campaign
await triggerCampaign({
email: customer.email,
campaign: 'post-purchase-onboarding',
variables: {
productName: order.line_items[0].title,
downloadLink: order.download_link
}
});
console.log(`Customer ${customer.email} added to CRM`);
}

Analytics tracking:

async function handleDownloadAccessed(data) {
const download = data.download;
// Send to Google Analytics
await trackEvent({
category: 'Downloads',
action: 'File Downloaded',
label: download.file.filename,
value: 1,
customDimensions: {
cd1: download.customer.email,
cd2: download.order_number,
cd3: download.location.country
}
});
// Send to custom analytics
await logDownload({
file_id: download.file.id,
customer: download.customer.email,
ip: download.ip_address,
country: download.location.country,
timestamp: new Date()
});
}

Handle webhooks efficiently:

Queue-based processing:

const Queue = require('bull');
const webhookQueue = new Queue('webhooks');
// Receive webhook
app.post('/webhooks/alva', async (req, res) => {
// Quick validation
if (!verifyWebhook(req.body, req.headers['x-alva-hmac-sha256'])) {
return res.status(401).send('Invalid');
}
// Add to queue (fast response)
await webhookQueue.add({
event: req.body.event,
data: req.body.data
});
// Respond immediately
res.status(200).send('Queued');
});
// Process in background
webhookQueue.process(async (job) => {
const { event, data } = job.data;
switch (event) {
case 'order.created':
await handleOrderCreated(data);
break;
case 'download.accessed':
await handleDownloadAccessed(data);
break;
// ... other events
}
});

API authentication:

Authorization: Bearer {api_key}
Content-Type: application/json

Get API key:

Settings → Advanced → API Access
[Generate API Key]
Key: ak_live_abc123def456...
Store: Environment variable (keep secret)

Common endpoints:

List files:

GET /api/files
Authorization: Bearer {api_key}
Query parameters:
?page=1&limit=50
?tags=course,premium
?unmapped=true
Response:
{
"files": [...],
"total": 247,
"page": 1,
"pages": 5
}

Get file details:

GET /api/files/{file_id}
Authorization: Bearer {api_key}
Response:
{
"id": "file_abc123",
"filename": "Chapter-1.pdf",
"size": 2500000,
"upload_date": "2024-01-15",
"products": ["prod_123"],
"tags": ["course", "chapter1"],
"downloads": 145
}

Create download link:

POST /api/downloads/generate
Authorization: Bearer {api_key}
Content-Type: application/json
{
"file_id": "file_abc123",
"customer_email": "customer@example.com",
"expiry_days": 7,
"download_limit": 3
}
Response:
{
"download_url": "https://yourshop.com/download?key=...",
"token": "abc123def456",
"expires_at": "2024-01-22T15:30:00Z",
"download_limit": 3
}

API rate limits:

Limits:
• 1000 requests/hour
• 100 requests/minute burst
Headers returned:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 847
X-RateLimit-Reset: 1610000000
429 response: Too Many Requests
Retry-After: 3600 (seconds)

Handle rate limits:

async function apiRequest(endpoint, options) {
const response = await fetch(endpoint, options);
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After');
console.log(`Rate limited. Retry after ${retryAfter}s`);
await sleep(retryAfter * 1000);
return apiRequest(endpoint, options); // Retry
}
return response.json();
}

Mailchimp automation:

// Add customer to Mailchimp after purchase
async function syncToMailchimp(orderData) {
const customer = orderData.order.customer;
const response = await fetch(
'https://us1.api.mailchimp.com/3.0/lists/{list_id}/members',
{
method: 'POST',
headers: {
'Authorization': 'Bearer ' + process.env.MAILCHIMP_API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({
email_address: customer.email,
status: 'subscribed',
merge_fields: {
FNAME: customer.first_name,
LNAME: customer.last_name
},
tags: ['digital-customer', 'alva-downloads']
})
}
);
console.log('Added to Mailchimp:', customer.email);
}

Connect to 5000+ apps:

Setup Zapier webhook:

1. Create Zap: Webhooks by Zapier (Trigger)
2. Get webhook URL: https://hooks.zapier.com/hooks/catch/...
3. Alva → Webhooks → Add webhook → Paste Zapier URL
4. Test webhook
5. Zapier: Add action (e.g., Google Sheets, Slack, etc.)
6. Enable Zap

Example Zaps:

• New order → Add row to Google Sheets
• Download accessed → Send Slack notification
• Order approved → Create Trello card
• File uploaded → Post to Discord

Visual automation:

Webhook trigger:

1. Make → Create scenario
2. Add webhook module
3. Copy webhook URL
4. Alva → Add webhook → Paste URL
5. Add action modules (any apps)
6. Activate scenario

Example scenarios:

• Order → Update Airtable → Send SMS via Twilio
• Download → Log to database → Send analytics to Slack
• New customer → Add to CRM → Assign to sales team

Full integration example:

Sync orders to custom database:

const express = require('express');
const app = express();
const db = require('./database');
app.use(express.json());
app.post('/webhooks/alva', async (req, res) => {
// Verify webhook
if (!verifyWebhook(req.body, req.headers['x-alva-hmac-sha256'])) {
return res.status(401).send('Invalid');
}
const { event, data } = req.body;
try {
switch (event) {
case 'order.created':
await handleOrderCreated(data);
break;
case 'download.accessed':
await logDownload(data);
break;
}
res.status(200).send('OK');
} catch (error) {
console.error('Webhook error:', error);
res.status(500).send('Error processing webhook');
}
});
async function handleOrderCreated(data) {
const order = data.order;
// Save to database
await db.orders.insert({
order_id: order.id,
order_number: order.number,
customer_email: order.customer.email,
total: order.total,
currency: order.currency,
created_at: order.created_at,
files: JSON.stringify(order.line_items.map(item => item.files)),
download_link: order.download_link
});
// Send custom notification
await sendNotification({
type: 'new_order',
order_number: order.number,
customer: order.customer.email,
total: order.total
});
}
async function logDownload(data) {
const download = data.download;
await db.downloads.insert({
file_id: download.file.id,
filename: download.file.filename,
customer_email: download.customer.email,
order_number: download.order_number,
ip_address: download.ip_address,
country: download.location.country,
timestamp: new Date(),
download_count: download.download_count
});
}
app.listen(3000);

View webhook delivery:

Settings → Webhooks → Logs
Columns:
• Timestamp
• Event type
• Endpoint URL
• HTTP status
• Response time
• Retry count
• Payload (expandable)

Filter logs:

☐ Failed deliveries only
☐ Specific event type
☐ Last 24 hours
☐ Specific endpoint

Failed webhook handling:

Automatic retries:

Retry strategy:
Attempt 1: Immediate
Attempt 2: After 5 seconds
Attempt 3: After 25 seconds (5^2)
Attempt 4: After 125 seconds (5^3)
Max retries: 3
Timeout: 10 seconds per attempt
Final failure: Logged, notification sent

Manual retry:

Webhook logs → Failed delivery → [Retry]
Resends webhook to endpoint

Debug checklist:

☐ Endpoint URL correct and accessible
☐ Server accepts POST requests
☐ Firewall allows webhooks
☐ SSL certificate valid (HTTPS required)
☐ Endpoint responds within 10 seconds
☐ Returns 200 OK status
☐ Check webhook logs for errors

Test endpoint:

Settings → Webhooks → Test Webhook
Sends test payload to endpoint
View response and errors

Fix verification:

1. Use correct webhook secret (from settings)
2. Hash entire raw request body (not parsed JSON)
3. Use timing-safe comparison
4. Check HMAC algorithm (SHA-256)
5. Ensure secret not expired/regenerated

Fast response:

✓ Respond 200 OK immediately
✓ Process in background queue
✓ Don't wait for long operations
✓ Timeout: < 1 second ideal
❌ Don't process synchronously
❌ Don't wait for external API calls
❌ Don't perform heavy computations

Idempotent processing:

Use unique webhook ID or order ID
Check if already processed
Skip if duplicate
Example:
const processed = await db.webhooks.find(webhook_id);
if (processed) {
return; // Already handled
}
// Process webhook...
await db.webhooks.insert({ webhook_id, processed: true });

Comprehensive logging:

✓ All webhook receipts
✓ Processing results
✓ Errors and exceptions
✓ Retry attempts
✓ Timestamp all events
Helps troubleshooting
Audit trail

Proactive monitoring:

✓ Alert on failed webhooks
✓ Monitor processing time
✓ Track error rates
✓ Set up health checks
✓ Review logs regularly

Testing approach:

✓ Test each event type
✓ Test with real payloads
✓ Test error scenarios
✓ Test retry logic
✓ Load test with high volume