Skip to main content

What are Webhooks?

Webhooks are HTTP callbacks that Zupy sends to your server when specific events occur in the loyalty program. Instead of continuously polling our API for changes, webhooks deliver real-time notifications about customer activities, point transactions, coupon usage, and tier changes directly to your application.

How Webhooks Work

1

Event Occurs

A customer action triggers an event (e.g., customer registers, earns points, redeems coupon)
2

Webhook Triggered

Zupy prepares the event payload with relevant customer and transaction data
3

HTTP Request Sent

Zupy sends a POST request to your configured webhook URL
4

Response Expected

Your endpoint should respond with HTTP 200 to confirm receipt
5

Retry Logic

Failed deliveries are retried with exponential backoff up to 5 times

Available Webhook Events

Zupy supports the following webhook events that you can configure in your company admin panel:

Customer Events

customer.registered

Triggered when a new customer joins the loyalty program
  • Use cases: Welcome email sequences, onboarding automation, CRM integration
  • Payload includes: Customer profile, tier assignment, welcome reward details

tier.upgraded

Triggered when a customer advances to a higher loyalty tier
  • Use cases: Congratulatory messages, tier benefit notifications, VIP treatment
  • Payload includes: Previous tier, new tier, qualification metrics

Transaction Events

points.earned

Triggered when a customer earns points from a transaction
  • Use cases: Transaction confirmations, progress notifications, gamification
  • Payload includes: Points earned, new balance, transaction details, next tier progress

Coupon Events

coupon.redeemed

Triggered when a customer converts points into a coupon
  • Use cases: Coupon delivery, inventory management, redemption analytics
  • Payload includes: Coupon code, reward details, points spent, expiration date

coupon.used

Triggered when a customer actually uses a coupon at your business
  • Use cases: Inventory updates, sales analytics, customer behavior tracking
  • Payload includes: Usage location, staff member, order details, discount applied

Webhook Configuration

Setting up Webhook URLs

Configure your webhook endpoints in the Zupy admin panel:
  1. Navigate to Settings > Webhooks
  2. Enter your endpoint URL (must be HTTPS)
  3. Select which events you want to receive
  4. Configure authentication (optional but recommended)
  5. Test the connection with a sample payload

Endpoint Requirements

Your webhook endpoint must meet these requirements:
const express = require('express');
const crypto = require('crypto');
const app = express();

app.use(express.json());

app.post('/zupy-webhooks', (req, res) => {
  // Verify signature (recommended)
  const signature = req.headers['x-zupy-signature'];
  const payload = JSON.stringify(req.body);
  
  if (!verifySignature(payload, signature)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }
  
  // Process the webhook
  const { event, company_id, customer, timestamp } = req.body;
  
  switch (event) {
    case 'customer.registered':
      handleCustomerRegistered(req.body);
      break;
    case 'points.earned':
      handlePointsEarned(req.body);
      break;
    case 'coupon.redeemed':
      handleCouponRedeemed(req.body);
      break;
    // ... handle other events
  }
  
  // Always return 200 for successful processing
  res.status(200).json({ received: true });
});

const verifySignature = (payload, signature) => {
  const secret = process.env.ZUPY_WEBHOOK_SECRET;
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  return signature === `sha256=${expectedSignature}`;
};

Security Best Practices

Signature Verification

Always verify webhook signatures to ensure requests are from Zupy:
1

Get Signature

Extract the X-Zupy-Signature header from the request
2

Compute HMAC

Create HMAC-SHA256 hash of the payload using your webhook secret
3

Compare

Use constant-time comparison to match signatures
4

Reject Invalid

Return 401 for requests with invalid signatures

Additional Security Measures

Configure webhook URLs with HTTPS to encrypt data in transit. Zupy rejects HTTP webhook URLs in production.
Restrict webhook endpoint access to Zupy’s IP ranges:
  • 52.44.102.0/24 (Primary)
  • 18.208.0.0/16 (Backup)
Handle duplicate webhooks gracefully by implementing idempotency keys based on event ID and timestamp.
Respond to webhooks within 10 seconds to avoid timeouts and retries.

Webhook Delivery and Retries

Delivery Guarantees

  • At-least-once delivery: You may receive duplicate webhooks
  • Best-effort ordering: Events are delivered in approximate order
  • 5 retry attempts: Failed deliveries are retried with exponential backoff
  • 10-second timeout: Your endpoint must respond within 10 seconds

Retry Schedule

AttemptDelayTotal Time
1stImmediate0s
2nd1 minute1m
3rd5 minutes6m
4th15 minutes21m
5th1 hour1h 21m

Response Codes

CodeBehavior
200-299Success - no retry
300-399Redirect - follow once, then retry
400-499Client error - no retry (except 408, 429)
500-599Server error - retry with backoff
TimeoutNo response - retry with backoff

Testing Webhooks

Using Webhook Testing Tools

# Install ngrok
npm install -g ngrok

# Expose local server
ngrok http 3000

# Use the HTTPS URL in Zupy admin
# Example: https://abc123.ngrok.io/zupy-webhooks

Manual Testing in Admin Panel

  1. Go to Settings > Webhooks
  2. Click Test Webhook next to your configured endpoint
  3. Select an event type to simulate
  4. Review the response and timing
  5. Check your server logs for received payload

Common Integration Patterns

Customer Relationship Management (CRM)

const handleCustomerRegistered = (webhook) => {
  const { customer, company_id } = webhook;
  
  // Sync with CRM
  await crmClient.createContact({
    email: customer.email,
    phone: customer.whatsapp,
    name: customer.name,
    loyaltyId: customer.member_id,
    tier: customer.tier,
    companyId: company_id,
    tags: ['loyalty-member', customer.tier]
  });
  
  // Trigger welcome sequence
  await emailService.triggerSequence('welcome', customer.email);
};

Marketing Automation

const handlePointsEarned = (webhook) => {
  const { customer, transaction, next_tier } = webhook;
  
  // Send progress update
  if (next_tier && next_tier.progress_percentage > 80) {
    await pushNotification.send(customer.member_id, {
      title: 'Quase lá! 🎉',
      body: `Você está a ${next_tier.points_needed} pontos do tier ${next_tier.tier_name}!`,
      action: 'open_rewards'
    });
  }
  
  // Update customer segments
  await marketingAutomation.updateSegment(customer.email, {
    loyaltyTier: customer.tier,
    totalPoints: transaction.new_balance,
    lastPurchase: transaction.timestamp
  });
};

Inventory Management

const handleCouponUsed = (webhook) => {
  const { usage, customer } = webhook;
  
  // Update inventory for product-free coupons
  if (usage.discount_type === 'product_free') {
    await inventory.decrementStock(usage.reward_name, 1);
    
    // Check if stock is low
    const currentStock = await inventory.getStock(usage.reward_name);
    if (currentStock < 5) {
      await alerts.send('low-stock', {
        product: usage.reward_name,
        stock: currentStock,
        location: usage.location
      });
    }
  }
  
  // Track customer behavior
  await analytics.track('coupon_used', {
    customerId: customer.member_id,
    rewardName: usage.reward_name,
    orderValue: usage.order_value,
    location: usage.location
  });
};

Troubleshooting

Common Issues

Possible Causes:
  • Endpoint not reachable (check firewall, DNS)
  • HTTPS certificate issues
  • Server returning non-200 status codes
  • Request timeout (response takes > 10s)
Solutions:
  • Test endpoint with curl or Postman
  • Check server logs for webhook requests
  • Verify SSL certificate validity
  • Optimize endpoint response time
Cause: Network issues or server errors can trigger retriesSolution: Implement idempotency using event ID + timestamp
const processedEvents = new Set();

app.post('/webhooks', (req, res) => {
  const eventKey = `${req.body.event}_${req.body.timestamp}`;
  
  if (processedEvents.has(eventKey)) {
    return res.status(200).json({ received: true });
  }
  
  processedEvents.add(eventKey);
  // Process webhook...
});
Possible Causes:
  • Wrong webhook secret
  • Payload modification (proxies, middleware)
  • Character encoding issues
Solutions:
  • Verify webhook secret in admin panel
  • Check raw payload before JSON parsing
  • Ensure UTF-8 encoding

Debug Mode

Enable debug mode in webhook settings to receive detailed delivery logs:
  • Request/response headers
  • Payload content
  • Response time and status
  • Retry attempts and outcomes
  • Error messages and stack traces

Rate Limits and Monitoring

Webhook Limits

  • Maximum endpoints per company: 10
  • Maximum events per endpoint: All available events
  • Delivery rate: Up to 100 webhooks/minute per endpoint
  • Payload size: Maximum 1MB per webhook

Monitoring Webhook Health

Track these metrics to ensure reliable webhook processing:

Success Rate

Monitor the percentage of successful deliveries (HTTP 200 responses)

Response Time

Track average response time to optimize performance

Error Rate

Monitor failed deliveries and common error patterns

Retry Frequency

Track how often webhooks require retries

Next Steps

Now that you understand how Zupy webhooks work, explore the specific webhook events: