Webhook Events API
Subscribfy sends real-time HTTP POST notifications to your endpoints when events occur. Use webhooks to sync customer data, trigger automations, or integrate with third-party systems.
Authentication
Each webhook request is signed using HMAC SHA-256. Verify the signature to ensure requests originate from Subscribfy.
Header | Description |
| HMAC SHA-256 hash of the request body |
| Unix timestamp when event occurred |
| Your Shopify store domain |
Subscribfy Token
Your unique secret token is used to sign all webhook requests. Generate it in Settings > Webhooks
Token is shown only once at generation - store it securely
If lost, regenerate a new token (invalidates the old one)
Never expose your token in client-side code
Signature Verification
PHP
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_SIGNATURE'] ?? '';
$expected = hash_hmac('sha256', $payload, $subscribfyToken);if (!hash_equals($expected, $signature)) {
http_response_code(401);
exit('Invalid signature');
}$event = json_decode($payload, true);
Node.js
const crypto = require('crypto');app.post('/webhook', (req, res) => {
const signature = req.headers['signature'];
const payload = JSON.stringify(req.body);
const expected = crypto
.createHmac('sha256', process.env.SUBSCRIBFY_TOKEN)
.update(payload)
.digest('hex'); if (signature !== expected) {
return res.status(401).send('Invalid signature');
} // Process event
const { topic, data } = req.body;
console.log(`Received: ${topic}`);
res.status(200).send('OK');
});
Python
import hmac
import hashlib
from flask import Flask, request@app.route('/webhook', methods=['POST'])
def webhook():
signature = request.headers.get('Signature', '')
payload = request.get_data()
expected = hmac.new(
SUBSCRIBFY_TOKEN.encode(),
payload,
hashlib.sha256
).hexdigest() if not hmac.compare_digest(signature, expected):
return 'Invalid signature', 401 event = request.get_json()
return 'OK', 200
Configuration
Go to Settings > Webhooks in the Subscribfy app
Select the event category (Wallet Pass, Membership, etc.)
Enter your endpoint URL(s) - separate multiple URLs with commas
Click Test Call to verify your endpoint
Click Save to activate
Event Reference
Wallet Pass Events
Topic | Trigger |
| New wallet pass generated for customer |
| Pass content updated (points, rewards, tier) |
| Pass installed on customer's device |
| Pass removed/uninstalled from device |
Membership Events
Topic | Trigger |
| New membership contract created |
| Contract modified (frequency, next date) |
| Membership paused by customer or admin |
| Paused/cancelled membership resumed |
| Membership cancelled |
| Payment processed successfully |
| Payment failed |
| Customer store credits modified |
Product Subscription Events
Topic | Trigger |
| New product subscription started |
| Subscription modified (products, shipping, frequency) |
| Subscription paused |
| Subscription resumed |
| Subscription cancelled |
| Renewal payment successful |
| Renewal payment failed |
Loyalty Events
Topic | Trigger |
| New loyalty rule created in admin |
| Loyalty rule settings modified |
| Customer completed a rule and earned reward |
| New tier created |
| Tier settings modified |
| Customer promoted to new tier |
| Customer demoted from tier |
| Customer points earned, spent, or adjusted |
| New coupon created |
| Customer redeemed a loyalty coupon |
Payload Structure
All webhooks follow this structure:
{
"topic": "event/type",
"data": {
"customer": { ... },
// Event-specific data
}
}
Customer Object
Included in all events:
"customer": {
"id": "gid://shopify/Customer/123456789",
"email": "customer@example.com",
"name": "John Doe",
"loyalty_points": 1250.00,
"store_credits": 25.50,
"birth_date": "1990-05-15",
"tier": {
"id": 1,
"name": "Gold"
}
} Example: Wallet Pass Created
{
"topic": "wallet_pass/created",
"data": {
"pass_instance": {
"status": "Active",
"serial_number": "955773794",
"created_at": "2026-01-22T10:30:00.000000Z",
"deleted_at": null
},
"customer": {
"id": "gid://shopify/Customer/424525265",
"email": "john@example.com",
"name": "John Doe",
"loyalty_points": 500.00,
"store_credits": 10.00,
"tier": { "id": 2, "name": "Silver" }
}
}
}Example: Membership Billing Success
{
"topic": "membership/billing_success",
"data": {
"contract": {
"id": "gid://shopify/SubscriptionContract/123",
"status": "active",
"next_billing_date": "2026-02-22",
"billing_policy": {
"interval": "month",
"interval_count": 1
}
},
"order": {
"id": "gid://shopify/Order/456789",
"total_price": "29.99",
"currency": "USD"
},
"customer": {
"id": "gid://shopify/Customer/123456",
"email": "member@example.com",
"name": "Jane Smith"
}
}
}Example: Loyalty Points Changed
{
"topic": "loyalty/points_changed",
"data": {
"change": {
"previous_balance": 500,
"new_balance": 750,
"difference": 250,
"reason": "Order completed",
"rule_name": "Points per dollar spent"
},
"customer": {
"id": "gid://shopify/Customer/789",
"email": "loyal@example.com",
"name": "Mike Johnson",
"loyalty_points": 750.00,
"tier": { "id": 3, "name": "Platinum" }
}
}
}Request Details
Property | Value |
Method |
|
Content-Type |
|
Timeout | 30 seconds |
Retries | None (ensure your endpoint is reliable) |
Best Practices
Respond quickly - Return 2xx status within 5 seconds, process asynchronously
Verify signatures - Always validate the Signature header
Handle duplicates - Use idempotency keys or check for duplicate events
Log everything - Store raw payloads for debugging
Use HTTPS - All webhook URLs must use HTTPS
Monitor failures - Set up alerts for failed webhook deliveries
Troubleshooting
Not receiving webhooks?
Verify your endpoint is publicly accessible and returns 2xx status. Check firewall rules.
Invalid signature errors?
Ensure you're using the raw request body (not parsed JSON) for HMAC calculation.
Missing events?
Check that the specific event type is enabled in Settings > Webhooks.
Test endpoint?
Use webhook.site or ngrok for local development testing.