Real-Time Loyalty Event Notifications
Webhooks allow you to receive real-time notifications when loyalty events occur in your Appstle account. Build reactive, event-driven integrations with detailed delivery logs and webhook management.
Webhooks are HTTP POST requests sent to a URL you configure (your endpoint). When an event happens, Appstle sends a POST request with event data to your endpoint.
Features:
- ✅ Real-time event notifications
- ✅ Comprehensive event payload data
- ✅ Multiple event types for different loyalty actions
- ✅ Configurable endpoint management
- ✅ Developer-friendly JSON payloads
- Log in to your Appstle dashboard
- Navigate to Settings → Webhooks
- Add your webhook endpoint URL
- Select which events you want to receive
- Return a
2xxstatus code (e.g.,200 OK) - Process events asynchronously in background
- Return success immediately
- Long processing times may cause timeouts
Appstle sends webhooks for the following loyalty events:
| Event Type | Description | Trigger |
|---|---|---|
point.earned | Points earned by customer | Purchase, review, social action, manual credit |
point.redeemed | Points redeemed for reward | Customer redeems points for discount/reward |
point.expired | Points expired | Points reached expiration date |
point.pending | Points pending approval | Order created but not fulfilled |
point.cancelled | Points cancelled/reversed | Order cancelled or refunded |
| Event Type | Description | Trigger |
|---|---|---|
vip.tier_upgraded | Customer moved to higher tier | Points/spend threshold met |
vip.tier_downgraded | Customer moved to lower tier | Tier renewal period ended |
vip.tier_expired | VIP tier expired | Tier expiration date reached |
| Event Type | Description | Trigger |
|---|---|---|
reward.created | New reward issued to customer | Points redeemed successfully |
reward.used | Reward/discount used | Discount code applied to order |
reward.expired | Reward expired | Reward expiration date reached |
| Event Type | Description | Trigger |
|---|---|---|
customer.enrolled | Customer enrolled in program | Customer joins loyalty program |
customer.status_changed | Customer status updated | Customer opts in/out of program |
| Event Type | Description | Trigger |
|---|---|---|
referral.created | Referral link created | Customer generates referral link |
referral.accepted | Referral accepted | Referred friend completes signup |
referral.completed | Referral completed | Referred friend makes qualifying purchase |
| Event Type | Description | Trigger |
|---|---|---|
review.submitted | Product review submitted | Customer submits product review |
review.approved | Review approved | Admin approves pending review |
review.rejected | Review rejected | Admin rejects pending review |
All webhooks follow this standard structure:
{
"type": "point.earned",
"shop": "example-store.myshopify.com",
"timestamp": "2026-01-31T10:30:00Z",
"data": {
// Event-specific payload (see detailed examples below)
}
}| Field | Type | Description |
|---|---|---|
type | String | Event type identifier |
shop | String | Shopify store domain |
timestamp | DateTime | ISO 8601 timestamp when event occurred |
data | Object | Event-specific data payload |
Sent when a customer earns points:
{
"type": "point.earned",
"shop": "example-store.myshopify.com",
"timestamp": "2026-01-31T10:30:00Z",
"data": {
"transactionId": 12345,
"customerId": "7890123456",
"customerEmail": "customer@example.com",
"points": 250,
"ruleName": "Purchase Points",
"ruleType": "ORDER_PLACED",
"orderId": "5234567890",
"orderName": "#1001",
"orderAmount": 125.00,
"status": "APPROVED",
"description": "Earned 250 points for order #1001",
"createdAt": "2026-01-31T10:30:00Z"
}
}Sent when a customer redeems points:
{
"type": "point.redeemed",
"shop": "example-store.myshopify.com",
"timestamp": "2026-01-31T11:15:00Z",
"data": {
"transactionId": 12346,
"customerId": "7890123456",
"customerEmail": "customer@example.com",
"pointsRedeemed": 500,
"rewardType": "DISCOUNT_CODE",
"rewardValue": 10.00,
"discountCode": "LOYALTY-ABC123",
"ruleName": "$10 Off Coupon",
"description": "Redeemed 500 points for $10 discount",
"createdAt": "2026-01-31T11:15:00Z"
}
}Sent when points expire:
{
"type": "point.expired",
"shop": "example-store.myshopify.com",
"timestamp": "2026-01-31T00:00:00Z",
"data": {
"transactionId": 12347,
"customerId": "7890123456",
"customerEmail": "customer@example.com",
"pointsExpired": 100,
"expirationDate": "2026-01-31",
"description": "100 points expired",
"createdAt": "2026-01-31T00:00:00Z"
}
}Sent when a customer is upgraded to a higher VIP tier:
{
"type": "vip.tier_upgraded",
"shop": "example-store.myshopify.com",
"timestamp": "2026-01-31T12:00:00Z",
"data": {
"customerId": "7890123456",
"customerEmail": "customer@example.com",
"previousTier": "Silver",
"newTier": "Gold",
"tierExpiry": "2027-01-31",
"totalPointsEarned": 5000,
"tierBenefits": [
"2x points on all purchases",
"Exclusive member discounts",
"Early access to sales"
],
"upgradedAt": "2026-01-31T12:00:00Z"
}
}Sent when a customer is downgraded to a lower VIP tier:
{
"type": "vip.tier_downgraded",
"shop": "example-store.myshopify.com",
"timestamp": "2026-01-31T00:00:00Z",
"data": {
"customerId": "7890123456",
"customerEmail": "customer@example.com",
"previousTier": "Gold",
"newTier": "Silver",
"reason": "TIER_RENEWAL_PERIOD_ENDED",
"downgradedAt": "2026-01-31T00:00:00Z"
}
}Sent when a new reward is issued to a customer:
{
"type": "reward.created",
"shop": "example-store.myshopify.com",
"timestamp": "2026-01-31T11:15:00Z",
"data": {
"rewardId": 98765,
"customerId": "7890123456",
"customerEmail": "customer@example.com",
"rewardType": "DISCOUNT_CODE",
"discountCode": "LOYALTY-ABC123",
"discountValue": 10.00,
"discountType": "FIXED_AMOUNT",
"expiryDate": "2026-02-28",
"status": "UNUSED",
"createdAt": "2026-01-31T11:15:00Z"
}
}Sent when a reward is used:
{
"type": "reward.used",
"shop": "example-store.myshopify.com",
"timestamp": "2026-01-31T14:30:00Z",
"data": {
"rewardId": 98765,
"customerId": "7890123456",
"customerEmail": "customer@example.com",
"discountCode": "LOYALTY-ABC123",
"orderId": "5234567891",
"orderName": "#1002",
"discountApplied": 10.00,
"usedAt": "2026-01-31T14:30:00Z"
}
}Sent when a customer enrolls in the loyalty program:
{
"type": "customer.enrolled",
"shop": "example-store.myshopify.com",
"timestamp": "2026-01-31T09:00:00Z",
"data": {
"customerId": "7890123456",
"customerEmail": "customer@example.com",
"customerName": "John Doe",
"initialTier": "Bronze",
"welcomePoints": 100,
"enrolledAt": "2026-01-31T09:00:00Z"
}
}Sent when a referral is completed (referred friend makes qualifying purchase):
{
"type": "referral.completed",
"shop": "example-store.myshopify.com",
"timestamp": "2026-01-31T15:45:00Z",
"data": {
"referrerId": "7890123456",
"referrerEmail": "customer@example.com",
"referredId": "7890123457",
"referredEmail": "friend@example.com",
"referrerReward": 500,
"referredReward": 250,
"orderId": "5234567892",
"orderAmount": 150.00,
"completedAt": "2026-01-31T15:45:00Z"
}
}Sent when a customer submits a product review:
{
"type": "review.submitted",
"shop": "example-store.myshopify.com",
"timestamp": "2026-01-31T16:20:00Z",
"data": {
"reviewId": 45678,
"customerId": "7890123456",
"customerEmail": "customer@example.com",
"productId": "1234567890",
"productTitle": "Premium Coffee Beans",
"rating": 5,
"title": "Excellent quality!",
"body": "Best coffee I've ever had. Highly recommend!",
"pointsEarned": 50,
"status": "PENDING",
"submittedAt": "2026-01-31T16:20:00Z"
}
}Your webhook endpoint should:
- Return quickly - Respond with
200 OKwithin 5 seconds - Process asynchronously - Queue the event for background processing
- Return success immediately - Don't wait for processing to complete
- Handle duplicates - Use event IDs to ensure idempotent processing
Example Response Handler (Node.js):
app.post('/webhooks/appstle-loyalty', (req, res) => {
const event = req.body;
// Return 200 immediately
res.status(200).send('OK');
// Process asynchronously
processEvent(event).catch(err => {
console.error('Error processing webhook:', err);
});
});
async function processEvent(event) {
switch(event.type) {
case 'point.earned':
await handlePointsEarned(event.data);
break;
case 'vip.tier_upgraded':
await handleTierUpgrade(event.data);
break;
// Handle other event types
}
}Use tools like ngrok or localtunnel to expose your local development server and test webhooks:
ngrok http 3000Then add your ngrok URL (e.g., https://abc123.ngrok.io/webhooks/appstle-loyalty) as a webhook endpoint in your Appstle dashboard.
You can trigger test events from your Appstle dashboard:
- Go to Settings → Webhooks
- Click on your endpoint
- Use the "Send Test Event" feature
- Select event type to test
- Verify your endpoint receives and processes the webhook correctly
| Issue | Solution |
|---|---|
| Timeouts | Return 200 OK immediately and process events asynchronously. Avoid long-running operations in your webhook handler. |
| Wrong status codes | Always return 2xx for successful receipt (even if you encounter business logic errors). Use 4xx only for malformed requests. |
| CSRF protection blocking webhooks | Exempt your webhook endpoint from CSRF checks in your web framework. |
| Duplicate events | Webhooks can be delivered more than once. Use the transaction ID or event ID to make your processing idempotent. |
| Missing events | Verify your webhook endpoint is publicly accessible (not behind VPN/firewall). Check firewall rules. |
- Check the Webhook Logs in your Appstle dashboard to see delivery attempts and responses
- Log the raw webhook payload to understand the exact data structure
- Verify your endpoint is publicly accessible (not behind VPN/firewall)
- Test with a simple endpoint that just logs and returns
200 OK - Use webhook testing tools like webhook.site to inspect payloads
Contact support@appstle.com with your endpoint URL and event details for assistance.
- Idempotency - Use transaction IDs to prevent duplicate processing
- Error Handling - Always return
200 OKeven if processing fails internally - Async Processing - Queue events for background processing
- Logging - Log all webhook receipts for debugging and audit trails
- Security - Validate webhook source (check shop domain matches)
- Monitoring - Track webhook processing failures and alert on issues
- Rate Limiting - Implement rate limiting on your endpoint to prevent abuse
Last updated: January 2026