Skip to main content

Webhooks Controller

Source: internal/api/v1/reviews/controllers/webhook.controller.js
Service: webhook service
Module: Reviews


Overview

The Webhooks controller handles external integration webhooks that trigger review requests. It processes payment completion events from Stripe and order events from Square to automatically send review requests to customers.

Key Capabilities

  • Stripe Charge Webhook - Process completed payment events
  • Square Order Webhook - Process completed order events
  • Auto Review Request - Trigger review requests on purchase
  • Payment Validation - Verify webhook authenticity

MongoDB Collections

CollectionOperationsPurpose
review-requestsCREATETrack triggered review requests
review-auto-request-configREADGet automation settings

Service Methods

1. newCharge()

Process Stripe charge webhook to trigger review request.

Endpoint: POST /reviews/webhook/stripe/charge

Controller Logic:

const newCharge = catchAsync(async (req, res) => {
const { stripe_billing: stripeBilling } = req.auth;
const chargeDetails = req.body;
await webhookService.newCharge({ chargeDetails, stripeBilling });

res.status(200).json({
success: true,
message: 'SUCCESS',
});
});

Webhook Payload (from Stripe):

{
"id": "ch_abc123",
"object": "charge",
"amount": 5000,
"currency": "usd",
"customer": "cus_xyz",
"description": "Payment for Order #1234",
"metadata": {
"account_id": "acc_123",
"order_id": "order_456"
},
"status": "succeeded"
}

Processing Logic:

  1. Extract charge details from webhook
  2. Verify Stripe signature (security)
  3. Check if charge succeeded
  4. Get account_id from metadata
  5. Check auto-review-request config
  6. Validate delay and conditions
  7. Schedule review request job
  8. Return success

Response:

{
"success": true,
"message": "SUCCESS"
}

2. newSquareupOrder()

Process Square order webhook to trigger review request.

Endpoint: POST /reviews/webhook/square/order

Controller Logic:

const newSquareupOrder = catchAsync(async (req, res, next) => {
let squareUpInfo = req.body;
await webhookService.newSquareupOrder({ squareUpInfo });
res.status(200).json({
success: true,
message: 'SUCCESS',
});
});

Webhook Payload (from Square):

{
"merchant_id": "merchant_123",
"type": "order.created",
"event_id": "event_abc",
"created_at": "2024-12-08T10:00:00Z",
"data": {
"type": "order",
"id": "order_xyz",
"object": {
"order": {
"id": "order_xyz",
"location_id": "loc_123",
"customer_id": "cust_456",
"state": "COMPLETED",
"total_money": {
"amount": 5000,
"currency": "USD"
}
}
}
}
}

Processing Logic:

  1. Extract order details
  2. Verify Square signature
  3. Check order state (must be COMPLETED)
  4. Map location_id to account_id
  5. Get customer details
  6. Check auto-review config
  7. Schedule review request
  8. Return success

Response:

{
"success": true,
"message": "SUCCESS"
}

Webhook Security

Stripe Signature Verification

const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const sig = req.headers['stripe-signature'];
const event = stripe.webhooks.constructEvent(req.body, sig, webhookSecret);

Square Signature Verification

const crypto = require('crypto');
const signature = req.headers['x-square-signature'];
const isValid = crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature));

Business Logic Flow

sequenceDiagram
participant Stripe/Square
participant Webhook
participant Service
participant MongoDB
participant QueueManager

Stripe/Square->>Webhook: POST /webhook (charge/order)
Webhook->>Webhook: Verify Signature
Webhook->>Service: newCharge/newSquareupOrder
Service->>MongoDB: Find auto-request config
MongoDB-->>Service: Config with delay_hours
Service->>MongoDB: Check recent requests
Service->>Service: Validate conditions
Service->>QueueManager: Schedule review request
QueueManager-->>Service: Job ID
Service-->>Webhook: Success
Webhook-->>Stripe/Square: 200 OK

Note over QueueManager: Wait delay_hours

QueueManager->>Service: Execute review request
Service->>Service: Get customer email/phone
Service->>Service: Generate review links
Service->>Service: Send email/SMS

Trigger Conditions

When Review Request Sends

  1. Charge Succeeded: Payment must be successful
  2. Config Enabled: auto-review-request must be active
  3. Delay Passed: Configured delay_hours elapsed
  4. Min Amount: Order meets minimum value (if set)
  5. Not Recent: Customer not requested in last 30 days
  6. Not Refunded: Order not refunded (if check enabled)

When Request Skipped

  • Payment failed or pending
  • Auto-request disabled for account
  • Customer requested recently
  • Order value below minimum
  • Order refunded
  • No customer email/phone found

Edge Cases & Error Handling

1. Invalid Webhook Signature:

// Stripe throws error if signature invalid
throw new Error('Invalid webhook signature');
// Returns 400 Bad Request

2. Missing account_id:

// Cannot process without account mapping
throw new Error('account_id not found in metadata');

3. No Auto-Request Config:

// Silently skip if not configured
if (!config || !config.enabled) {
return; // No error, just skip
}

4. Duplicate Webhook:

// Idempotency check using event_id
const existing = await ReviewRequest.findOne({ event_id });
if (existing) {
return { success: true, message: 'Already processed' };
}

5. Customer Not Found:

// Skip if cannot find customer details
if (!customer || (!customer.email && !customer.phone)) {
console.log('Customer contact info missing, skipping');
return;
}

Webhook URLs

Production Endpoints

Stripe Webhooks:
POST https://api.dashclicks.com/api/v1/v1/reviews/webhook/stripe/charge

Square Webhooks:
POST https://api.dashclicks.com/api/v1/v1/reviews/webhook/square/order

Testing Webhooks

Stripe CLI:

stripe listen --forward-to localhost:5002/api/v1/v1/reviews/webhook/stripe/charge
stripe trigger charge.succeeded

Square Sandbox:

# Use Square Sandbox webhook URLs
# Test events via Square Developer Dashboard

Authorization

Authentication:

  • ❌ No JWT required (external webhooks)
  • ✅ Signature verification required
  • ✅ HTTPS only

Security Measures:

  1. Signature Verification: Validates webhook source
  2. HTTPS Requirement: Encrypted transmission
  3. Idempotency: Prevents duplicate processing
  4. Rate Limiting: Prevents abuse

Integration Points

Triggered By:

  • Stripe: Payment completion events
  • Square: Order fulfillment events
  • Custom Integrations: Can POST to webhook URL

Triggers:

  • Queue Manager: Schedules delayed review request
  • Notification Service: Sends email/SMS
  • Review Requests Collection: Creates tracking record

Important Notes

Webhook Response Time

  • ⚠️ Must respond within 5 seconds
  • Use async processing for heavy operations
  • Return 200 immediately
  • Process in background queue

Retry Logic

  • Stripe retries failed webhooks (exponential backoff)
  • Square retries up to 3 times
  • Return 200 even if skipping request
  • Only return error if webhook processing fails

Metadata Requirements

Stripe Charges:

// Include in charge metadata
{
"account_id": "acc_123",
"customer_email": "customer@example.com",
"customer_phone": "+1234567890"
}

Square Orders:

// Map location_id to account_id in database
await LocationMapping.findOne({ location_id: 'loc_123' });


Version: 1.0
Last Updated: December 2024
Status: ✅ Production Ready

💬

Documentation Assistant

Ask me anything about the docs

Hi! I'm your documentation assistant. Ask me anything about the docs!

I can help you with:
- Code examples
- Configuration details
- Troubleshooting
- Best practices

Try asking: How do I configure the API?
09:31 AM