Webhooks Advanced
Technical guide for implementing and consuming Subscribfy webhooks
Real-time event notifications for building integrations and automations.
Webhook Overview
Subscribfy sends HTTP POST requests to your endpoint when events occur. Your endpoint must:
- Accept POST requests
- Use HTTPS
- Return 2xx status within 30 seconds
- Handle duplicate events gracefully
Available Events
Subscription Events
| Event | Description |
|---|---|
subscription.created | New subscription created |
subscription.activated | Subscription became active |
subscription.updated | Subscription details changed |
subscription.paused | Subscription paused |
subscription.resumed | Subscription resumed |
subscription.cancelled | Subscription cancelled |
Billing Events
| Event | Description |
|---|---|
billing.pending | Billing attempt scheduled |
billing.success | Payment successful |
billing.failed | Payment failed |
billing.retry_scheduled | Retry scheduled after failure |
Member Events
| Event | Description |
|---|---|
member.created | New member registered |
member.updated | Member data updated |
member.tier_changed | Loyalty tier changed |
member.points_changed | Points balance changed |
Store Credit Events
| Event | Description |
|---|---|
store_credit.added | Credits added to balance |
store_credit.deducted | Credits used or removed |
Payload Structure
All webhooks follow this structure:
{
"event": "subscription.created",
"timestamp": "2024-01-15T10:30:00Z",
"webhook_id": "wh_abc123",
"data": {
// Event-specific data
}
}Common Fields
| Field | Type | Description |
|---|---|---|
event | string | Event type identifier |
timestamp | ISO 8601 | When event occurred |
webhook_id | string | Unique webhook delivery ID |
data | object | Event-specific payload |
Payload Examples
subscription.created
{
"event": "subscription.created",
"timestamp": "2024-01-15T10:30:00Z",
"webhook_id": "wh_abc123",
"data": {
"subscription_id": "sub_12345",
"shopify_subscription_id": "gid://shopify/SubscriptionContract/12345",
"customer": {
"id": "cust_67890",
"shopify_id": "gid://shopify/Customer/67890",
"email": "customer@example.com",
"first_name": "John",
"last_name": "Doe"
},
"plan": {
"id": "plan_111",
"name": "VIP Membership",
"price": 29.99,
"currency": "USD",
"billing_frequency": "monthly"
},
"status": "active",
"created_at": "2024-01-15T10:30:00Z",
"next_billing_date": "2024-02-15T10:30:00Z"
}
}billing.success
{
"event": "billing.success",
"timestamp": "2024-02-15T10:30:00Z",
"webhook_id": "wh_def456",
"data": {
"billing_attempt_id": "ba_789",
"subscription_id": "sub_12345",
"customer": {
"id": "cust_67890",
"email": "customer@example.com"
},
"order": {
"id": "order_111",
"shopify_id": "gid://shopify/Order/111",
"order_number": "#1001",
"total": 29.99,
"currency": "USD"
},
"amount": 29.99,
"currency": "USD",
"payment_method": {
"type": "credit_card",
"last_four": "4242",
"brand": "visa"
},
"next_billing_date": "2024-03-15T10:30:00Z"
}
}billing.failed
{
"event": "billing.failed",
"timestamp": "2024-02-15T10:30:00Z",
"webhook_id": "wh_ghi789",
"data": {
"billing_attempt_id": "ba_790",
"subscription_id": "sub_12345",
"customer": {
"id": "cust_67890",
"email": "customer@example.com"
},
"amount": 29.99,
"currency": "USD",
"error": {
"code": "card_declined",
"message": "Your card was declined.",
"decline_code": "insufficient_funds"
},
"retry_scheduled": true,
"retry_date": "2024-02-18T10:30:00Z",
"attempts_remaining": 3
}
}member.points_changed
{
"event": "member.points_changed",
"timestamp": "2024-01-20T14:00:00Z",
"webhook_id": "wh_jkl012",
"data": {
"member_id": "mem_12345",
"customer": {
"id": "cust_67890",
"email": "customer@example.com"
},
"points": {
"previous_balance": 500,
"new_balance": 600,
"change": 100
},
"reason": "order_placed",
"order_id": "order_222"
}
}Implementation Guide
Endpoint Requirements
POST https://your-domain.com/webhooks/subscribfy
Content-Type: application/json
X-Subscribfy-Signature: sha256=...Signature Verification
Verify webhook authenticity using HMAC-SHA256:
const crypto = require('crypto');
function verifyWebhook(payload, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return `sha256=${expected}` === signature;
}
// Express.js example
app.post('/webhooks/subscribfy', (req, res) => {
const signature = req.headers['x-subscribfy-signature'];
const isValid = verifyWebhook(
JSON.stringify(req.body),
signature,
process.env.WEBHOOK_SECRET
);
if (!isValid) {
return res.status(401).send('Invalid signature');
}
// Process webhook
processEvent(req.body);
res.status(200).send('OK');
});Idempotency
Webhooks may be delivered multiple times. Use webhook_id for deduplication:
const processedWebhooks = new Set();
function processEvent(webhook) {
if (processedWebhooks.has(webhook.webhook_id)) {
console.log('Duplicate webhook, skipping');
return;
}
processedWebhooks.add(webhook.webhook_id);
// Process the event
switch (webhook.event) {
case 'subscription.created':
handleSubscriptionCreated(webhook.data);
break;
case 'billing.failed':
handleBillingFailed(webhook.data);
break;
// ...
}
}Retry Policy
If your endpoint fails to respond with 2xx:
| Attempt | Delay |
|---|---|
| 1st retry | 1 minute |
| 2nd retry | 5 minutes |
| 3rd retry | 30 minutes |
| 4th retry | 2 hours |
| 5th retry | 12 hours |
| Final | 24 hours |
After 6 failed attempts, the webhook is marked as failed.
Testing Webhooks
Use Test Mode
Send test webhooks from Subscribfy:
- Go to Settings → Webhooks
- Click Test next to your endpoint
- Select event type
- Click Send Test
Local Development
For local testing, use a tunnel service:
# Using ngrok
ngrok http 3000
# Your endpoint becomes
# https://abc123.ngrok.io/webhooks/subscribfyWebhook Logs
View webhook delivery logs in Subscribfy:
- Go to Settings → Webhooks
- Click on your endpoint
- View Delivery History
Each log shows:
- Event type
- Delivery status
- Response code
- Response time
- Payload (click to expand)
Best Practices
Common Use Cases
Sync to Data Warehouse
async function syncToWarehouse(webhook) {
const { event, data } = webhook;
if (event === 'subscription.created') {
await warehouse.insert('subscriptions', {
id: data.subscription_id,
customer_email: data.customer.email,
plan: data.plan.name,
price: data.plan.price,
created_at: data.created_at
});
}
}Trigger Email Flows
async function triggerEmail(webhook) {
if (webhook.event === 'billing.failed') {
await emailService.send({
template: 'payment_failed',
to: webhook.data.customer.email,
data: {
amount: webhook.data.amount,
retry_date: webhook.data.retry_date
}
});
}
}Update CRM
async function updateCRM(webhook) {
if (webhook.event === 'subscription.cancelled') {
await crm.updateContact(webhook.data.customer.email, {
subscription_status: 'cancelled',
cancel_reason: webhook.data.cancel_reason,
cancelled_at: webhook.timestamp
});
}
}Troubleshooting
Was this page helpful?