Webhook Events API
Real-time HTTP notifications for customer, subscription, and loyalty events. Includes authentication, payload examples, and code samples.
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 |
|---|---|
Signature | HMAC SHA-256 hash of the request body |
Timestamp | Unix timestamp when event occurred |
Shop | 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
$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);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');
});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', 200Configuration
- 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 |
|---|---|
wallet_pass/created | New wallet pass generated for customer |
wallet_pass/updated | Pass content updated (points, rewards, tier) |
wallet_pass/installed | Pass installed on customer's device |
wallet_pass/uninstalled | Pass removed/uninstalled from device |
Membership Events
| Topic | Trigger |
|---|---|
membership/created | New membership contract created |
membership/updated | Contract modified (frequency, next date) |
membership/paused | Membership paused by customer or admin |
membership/resumed | Paused/cancelled membership resumed |
membership/cancelled | Membership cancelled |
membership/billing_success | Payment processed successfully |
membership/billing_failed | Payment failed |
store_credit/changed | Customer store credits modified |
Product Subscription Events
| Topic | Trigger |
|---|---|
product_subscription/created | New product subscription started |
product_subscription/updated | Subscription modified (products, shipping, frequency) |
product_subscription/paused | Subscription paused |
product_subscription/resumed | Subscription resumed |
product_subscription/cancelled | Subscription cancelled |
product_subscription/billing_success | Renewal payment successful |
product_subscription/billing_failed | Renewal payment failed |
Loyalty Events
| Topic | Trigger |
|---|---|
loyalty/rule_created | New loyalty rule created in admin |
loyalty/rule_updated | Loyalty rule settings modified |
loyalty/rule_completed | Customer completed a rule and earned reward |
loyalty/tier_created | New tier created |
loyalty/tier_updated | Tier settings modified |
loyalty/tier_promoted | Customer promoted to new tier |
loyalty/tier_demoted | Customer demoted from tier |
loyalty/points_changed | Customer points earned, spent, or adjusted |
loyalty/coupon_created | New coupon created |
loyalty/coupon_redeemed | Customer redeemed a loyalty coupon |
Payload Structure
All webhooks follow this structure:
{
"topic": "event/type",
"data": {
"customer": { ... }
}
}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.0,
"store_credits": 10.0,
"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.0,
"tier": {
"id": 3,
"name": "Platinum"
}
}
}
}Request Details
| Property | Value |
|---|---|
| Method | POST |
| Content-Type | application/json |
| 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.