Receive real-time HTTP notifications when membership events occur. Appstle Memberships webhooks are powered by Svix — enterprise-grade webhook infrastructure with automatic retries, signature verification, and delivery monitoring.
- In your Appstle Memberships admin, go to Settings → Webhooks
- Click Add Endpoint and enter your HTTPS endpoint URL
- Select which events you want to receive (or subscribe to all)
- Save — your endpoint will start receiving events immediately
ℹ️ Requires Webhook Access: Webhooks are available on paid plans. Contact support@appstle.com to enable.
Webhooks are HTTP POST requests sent to your endpoint whenever a membership event occurs. Your endpoint must:
- Be publicly accessible via HTTPS
- Return a
2xxstatus code within the timeout window - Process events asynchronously (queue for background processing)
Powered by Svix:
- ✅ Automatic retries with exponential backoff
- ✅ Cryptographic signature verification
- ✅ Detailed delivery logs and replay
- ✅ Developer dashboard for monitoring
| Event Type | Description |
|---|---|
membership.created | New membership contract created |
membership.updated | Membership details modified |
membership.activated | Membership activated |
membership.paused | Membership paused |
membership.cancelled | Membership cancelled |
membership.expired | Membership expired at end of term |
membership.swap-product | Product/plan in membership changed |
membership.next-order-date-changed | Next renewal date rescheduled |
membership.billing-interval-changed | Billing frequency changed |
membership.billing-success | Renewal payment processed successfully |
membership.billing-failure | Renewal payment failed |
All webhooks follow this standard structure:
{
"type": "membership.created",
"data": {
// Event-specific payload
}
}Events membership.created, membership.updated, membership.activated, membership.paused, membership.cancelled, membership.expired, membership.swap-product, membership.next-order-date-changed, and membership.billing-interval-changed all send a full membership contract payload.
📄 Membership Contract Payload Example
{
"type": "membership.created",
"data": {
"id": "gid://shopify/SubscriptionContract/12345",
"createdAt": "2026-01-15T10:30:00Z",
"updatedAt": "2026-01-15T10:30:00Z",
"nextBillingDate": "2026-02-15",
"status": "ACTIVE",
"billingPolicy": {
"interval": "MONTH",
"intervalCount": 1,
"anchors": [],
"maxCycles": 12,
"minCycles": 1
},
"deliveryPolicy": {
"interval": "MONTH",
"intervalCount": 1,
"anchors": []
},
"lines": {
"nodes": [
{
"id": "gid://shopify/SubscriptionLine/67890",
"productId": "gid://shopify/Product/11111",
"variantId": "gid://shopify/ProductVariant/22222",
"sellingPlanId": "gid://shopify/SellingPlan/33333",
"sellingPlanName": "Gold Member — Monthly",
"title": "Gold Membership",
"variantTitle": "Monthly",
"quantity": 1,
"currentPrice": {
"amount": "29.00",
"currencyCode": "USD"
}
}
]
},
"customer": {
"id": "gid://shopify/Customer/55555",
"email": "member@example.com",
"displayName": "Jane Doe",
"firstName": "Jane",
"lastName": "Doe",
"phone": "+1-555-123-4567"
},
"originOrder": {
"id": "gid://shopify/Order/77777",
"name": "#1001"
},
"deliveryPrice": {
"amount": "0.00",
"currencyCode": "USD"
},
"lastPaymentStatus": "SUCCEEDED",
"note": null,
"customAttributes": []
}
}membership.billing-success and membership.billing-failure send billing attempt data.
✅ membership.billing-success Payload
{
"type": "membership.billing-success",
"data": {
"id": 98765,
"shop": "example-store.myshopify.com",
"billingAttemptId": "gid://shopify/SubscriptionBillingAttempt/99999",
"contractId": 12345,
"status": "SUCCESS",
"billingDate": "2026-02-15T00:00:00Z",
"attemptTime": "2026-02-15T10:30:00Z",
"attemptCount": 1,
"orderId": 77778,
"orderName": "#1002",
"orderAmount": 29.00,
"retryingNeeded": false,
"billingAttemptResponseMessage": null
}
}❌ membership.billing-failure Payload
{
"type": "membership.billing-failure",
"data": {
"id": 98766,
"shop": "example-store.myshopify.com",
"billingAttemptId": "gid://shopify/SubscriptionBillingAttempt/99998",
"contractId": 12345,
"status": "FAILURE",
"billingDate": "2026-03-15T00:00:00Z",
"attemptTime": "2026-03-15T10:30:00Z",
"attemptCount": 1,
"orderId": null,
"orderName": null,
"orderAmount": null,
"retryingNeeded": true,
"billingAttemptResponseMessage": "INVALID_PAYMENT_METHOD: The payment method is invalid."
}
}Common billingAttemptResponseMessage values:
INVALID_PAYMENT_METHOD— Payment method is expired or invalidINSUFFICIENT_FUNDS— Insufficient account balanceCARD_DECLINED— Card declined by issuerAUTHENTICATION_REQUIRED— Customer must re-authenticate paymentEXPIRED_PAYMENT_METHOD— Payment method has expired
Every webhook request is signed by Svix. Always verify the signature before processing the payload.
Svix includes these headers on every request:
svix-id— Unique message ID (use for idempotency)svix-timestamp— Unix timestamp of when the message was sentsvix-signature— HMAC-SHA256 signature
Find your webhook signing secret in your Appstle dashboard under Settings → Webhooks → [your endpoint].
const { Webhook } = require('svix');
const secret = 'whsec_your_signing_secret';
app.post('/webhooks/appstle-memberships', express.raw({ type: 'application/json' }), (req, res) => {
const wh = new Webhook(secret);
let event;
try {
event = wh.verify(req.body, {
'svix-id': req.headers['svix-id'],
'svix-timestamp': req.headers['svix-timestamp'],
'svix-signature': req.headers['svix-signature'],
});
} catch (err) {
return res.status(400).send('Webhook signature verification failed');
}
switch (event.type) {
case 'membership.created':
// Handle new membership
break;
case 'membership.billing-failure':
// Trigger dunning flow
break;
case 'membership.cancelled':
// Revoke member access
break;
}
res.status(200).send('OK');
});from svix.webhooks import Webhook, WebhookVerificationError
secret = "whsec_your_signing_secret"
@app.route('/webhooks/appstle-memberships', methods=['POST'])
def webhook():
headers = {
"svix-id": request.headers.get("svix-id"),
"svix-timestamp": request.headers.get("svix-timestamp"),
"svix-signature": request.headers.get("svix-signature"),
}
try:
wh = Webhook(secret)
event = wh.verify(request.data, headers)
except WebhookVerificationError:
return "Verification failed", 400
if event["type"] == "membership.billing-failure":
# trigger dunning logic
pass
return "OK", 200See Svix docs for Ruby, Go, PHP, Java, and C# examples.
If your endpoint returns a non-2xx status or times out, Svix retries with exponential backoff across 5 attempts over 3 days. View delivery history and manually replay events from your Appstle dashboard under Settings → Webhooks → Message Logs.
Webhooks can be delivered more than once. Use the svix-id header as an idempotency key to safely deduplicate events in your database.
Use ngrok to expose your local server:
ngrok http 3000
# Then add https://your-id.ngrok.io/webhooks/appstle-memberships as your endpoint| Issue | Solution |
|---|---|
| Signature verification fails | Use the raw request body (before JSON parsing). Verify you're using the correct secret from the dashboard. |
| Endpoint timing out | Return 200 OK immediately, then process async. |
| Not receiving events | Confirm the webhook integration is enabled in Settings and your endpoint is publicly accessible. |
| Duplicate events | Use svix-id header for deduplication. |
Need help? Contact support@appstle.com