# 🔔 Webhooks div h3 Real-Time Loyalty Event Notifications p 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. div 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 ## 🚀 Getting Started div div ### 1️⃣ Configure Your Endpoint - Log in to your Appstle dashboard - Navigate to **Settings** → **Webhooks** - Add your webhook endpoint URL - Select which events you want to receive div ### 2️⃣ Respond Quickly - Return a `2xx` status code (e.g., `200 OK`) - Process events **asynchronously** in background - Return success immediately - Long processing times may cause timeouts div ### 3️⃣ Handle Events - Parse the JSON payload - Validate the event type - Process the event data - Update your systems accordingly ## 📡 Event Types div Appstle sends webhooks for the following **loyalty events**: ### Point Transaction 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 | ### VIP Tier Events | 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 | ### Reward & Redemption Events | 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 | ### Customer Events | 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 | ### Referral Events | 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 | ### Review Events | 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 | ## Webhook Payload Structure All webhooks follow this standard structure: ```json { "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 | ## Complete Webhook Payloads ### Point Transaction Events #### point.earned Sent when a customer earns points: ```json { "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" } } ``` #### point.redeemed Sent when a customer redeems points: ```json { "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" } } ``` #### point.expired Sent when points expire: ```json { "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" } } ``` ### VIP Tier Events #### vip.tier_upgraded Sent when a customer is upgraded to a higher VIP tier: ```json { "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" } } ``` #### vip.tier_downgraded Sent when a customer is downgraded to a lower VIP tier: ```json { "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" } } ``` ### Reward Events #### reward.created Sent when a new reward is issued to a customer: ```json { "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" } } ``` #### reward.used Sent when a reward is used: ```json { "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" } } ``` ### Customer Events #### customer.enrolled Sent when a customer enrolls in the loyalty program: ```json { "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" } } ``` ### Referral Events #### referral.completed Sent when a referral is completed (referred friend makes qualifying purchase): ```json { "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" } } ``` ### Review Events #### review.submitted Sent when a customer submits a product review: ```json { "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" } } ``` ## Response Requirements Your webhook endpoint should: 1. **Return quickly** - Respond with `200 OK` within 5 seconds 2. **Process asynchronously** - Queue the event for background processing 3. **Return success immediately** - Don't wait for processing to complete 4. **Handle duplicates** - Use event IDs to ensure idempotent processing **Example Response Handler (Node.js):** ```javascript 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 } } ``` ## Testing Webhooks ### Local Development Use tools like [ngrok](https://ngrok.com/) or [localtunnel](https://localtunnel.me/) to expose your local development server and test webhooks: ```bash ngrok http 3000 ``` Then add your ngrok URL (e.g., `https://abc123.ngrok.io/webhooks/appstle-loyalty`) as a webhook endpoint in your Appstle dashboard. ### Test Events You can trigger test events from your Appstle dashboard: 1. Go to **Settings** → **Webhooks** 2. Click on your endpoint 3. Use the "Send Test Event" feature 4. Select event type to test 5. Verify your endpoint receives and processes the webhook correctly ## Troubleshooting ### Common Issues | 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. | ### Debugging Tips - 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](https://webhook.site) to inspect payloads ### Need Help? Contact support@appstle.com with your endpoint URL and event details for assistance. ## Best Practices 1. **Idempotency** - Use transaction IDs to prevent duplicate processing 2. **Error Handling** - Always return `200 OK` even if processing fails internally 3. **Async Processing** - Queue events for background processing 4. **Logging** - Log all webhook receipts for debugging and audit trails 5. **Security** - Validate webhook source (check shop domain matches) 6. **Monitoring** - Track webhook processing failures and alert on issues 7. **Rate Limiting** - Implement rate limiting on your endpoint to prevent abuse *Last updated: January 2026*