Skip to content
Last updated

🔔 Webhooks

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

🚀 Getting Started

1️⃣ Configure Your Endpoint

  • Log in to your Appstle dashboard
  • Navigate to SettingsWebhooks
  • Add your webhook endpoint URL
  • Select which events you want to receive

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

3️⃣ Handle Events

  • Parse the JSON payload
  • Validate the event type
  • Process the event data
  • Update your systems accordingly

📡 Event Types

Appstle sends webhooks for the following loyalty events:

Point Transaction Events

Event TypeDescriptionTrigger
point.earnedPoints earned by customerPurchase, review, social action, manual credit
point.redeemedPoints redeemed for rewardCustomer redeems points for discount/reward
point.expiredPoints expiredPoints reached expiration date
point.pendingPoints pending approvalOrder created but not fulfilled
point.cancelledPoints cancelled/reversedOrder cancelled or refunded

VIP Tier Events

Event TypeDescriptionTrigger
vip.tier_upgradedCustomer moved to higher tierPoints/spend threshold met
vip.tier_downgradedCustomer moved to lower tierTier renewal period ended
vip.tier_expiredVIP tier expiredTier expiration date reached

Reward & Redemption Events

Event TypeDescriptionTrigger
reward.createdNew reward issued to customerPoints redeemed successfully
reward.usedReward/discount usedDiscount code applied to order
reward.expiredReward expiredReward expiration date reached

Customer Events

Event TypeDescriptionTrigger
customer.enrolledCustomer enrolled in programCustomer joins loyalty program
customer.status_changedCustomer status updatedCustomer opts in/out of program

Referral Events

Event TypeDescriptionTrigger
referral.createdReferral link createdCustomer generates referral link
referral.acceptedReferral acceptedReferred friend completes signup
referral.completedReferral completedReferred friend makes qualifying purchase

Review Events

Event TypeDescriptionTrigger
review.submittedProduct review submittedCustomer submits product review
review.approvedReview approvedAdmin approves pending review
review.rejectedReview rejectedAdmin rejects pending review

Webhook Payload Structure

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)
  }
}
FieldTypeDescription
typeStringEvent type identifier
shopStringShopify store domain
timestampDateTimeISO 8601 timestamp when event occurred
dataObjectEvent-specific data payload

Complete Webhook Payloads

Point Transaction Events

point.earned

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"
  }
}

point.redeemed

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"
  }
}

point.expired

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"
  }
}

VIP Tier Events

vip.tier_upgraded

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"
  }
}

vip.tier_downgraded

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"
  }
}

Reward Events

reward.created

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"
  }
}

reward.used

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"
  }
}

Customer Events

customer.enrolled

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"
  }
}

Referral Events

referral.completed

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"
  }
}

Review Events

review.submitted

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"
  }
}

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):

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 or localtunnel to expose your local development server and test webhooks:

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 SettingsWebhooks
  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

IssueSolution
TimeoutsReturn 200 OK immediately and process events asynchronously. Avoid long-running operations in your webhook handler.
Wrong status codesAlways return 2xx for successful receipt (even if you encounter business logic errors). Use 4xx only for malformed requests.
CSRF protection blocking webhooksExempt your webhook endpoint from CSRF checks in your web framework.
Duplicate eventsWebhooks can be delivered more than once. Use the transaction ID or event ID to make your processing idempotent.
Missing eventsVerify 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 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