Webhook Setup
Relevant source files
The following files were used as context for generating this wiki page:
Webhooks are a critical component of the QashierWise backend, enabling real-time communication with external services. The system is configured to receive events from two primary sources: Meta for WhatsApp Business Account activities and Polar.sh for subscription management. These webhooks allow the application to react instantly to events such as incoming messages, message status changes, and subscription updates, ensuring data consistency and enabling interactive features.
The webhook implementation includes security measures like token and signature verification, multi-tenant routing to associate incoming data with the correct user account, and robust logging for debugging and monitoring purposes.
Architecture Overview
The webhook system is designed around two main public-facing endpoints, each dedicated to a specific service. When an event occurs in an external service (Meta or Polar.sh), it sends an HTTP POST request to the corresponding endpoint in the QashierWise API. The controller then validates the request, processes the payload, and triggers relevant business logic, such as updating the database and broadcasting real-time events to the frontend.
This diagram illustrates the high-level flow of incoming webhooks from external services to the application.
Webhook Endpoints
The application exposes the following public endpoints to receive webhook events. These routes are defined outside the auth:sanctum middleware group to be accessible by the external services.
| Method | URI | Controller Action | Description |
|---|---|---|---|
GET | /api/whatsapp/webhook | WhatsAppWebhookController@verify | Handles the one-time verification challenge from Meta. |
POST | /api/whatsapp/webhook | WhatsAppWebhookController@handle | Receives all WhatsApp-related events. |
POST | /api/webhooks/polar | PolarWebhookController@handle | Receives all Polar.sh subscription-related events. |
Sources: routes/api.php:19-25
WhatsApp Webhook
The WhatsApp webhook is responsible for handling all events related to the connected WhatsApp Business Account, including incoming messages, message status updates, and template status changes.
Sources: app/Http/Controllers/Api/WhatsAppWebhookController.php
Verification
Before Meta sends any events, it performs a verification step by sending a GET request to the webhook URL. The verify method handles this challenge.
- It receives
hub.mode,hub.verify_token, andhub.challengeas query parameters. - It checks if
hub.modeissubscribeand if thehub.verify_tokenmatches the value stored in thewhatsapp.webhook_verify_tokenconfiguration. - If both are valid, it responds with the
hub.challengevalue and a200 OKstatus. Otherwise, it responds with403 Forbidden.
Sources: app/Http/Controllers/Api/WhatsAppWebhookController.php:33-53
Event Handling
All subsequent events are sent via POST requests to the same endpoint and are processed by the handle method.
Multi-Tenant Routing
A key feature of the webhook handler is its ability to route incoming events to the correct user account. This is achieved by extracting the phone_number_id from the webhook payload and using the WhatsAppAccountService to find the corresponding WhatsAppAccount in the database. If no account matches the phone_number_id, the event is logged and skipped, ensuring data isolation between tenants.
// app/Http/Controllers/Api/WhatsAppWebhookController.php:111-118
// Find WhatsApp account using the service for multi-tenant routing
$whatsappAccount = $this->whatsAppAccountService->getAccountByPhoneNumberId($phoneNumberId);
if (!$whatsappAccount) {
// Log and skip processing for unknown phone numbers (Requirement 5.2)
Log::info('Webhook received for unknown phone number, skipping processing', [
'phone_number_id' => $phoneNumberId,
'waba_id' => $wabaId,
]);
continue;
}
Sources: app/Http/Controllers/Api/WhatsAppWebhookController.php:111-125, tests/Unit/Controllers/WhatsAppWebhookControllerPropertyTest.php:61-109
Incoming Message Flow
The following diagram shows the sequence of events when a new message is received from a customer.
Handled Event Fields
The controller processes different event types based on the field property in the payload.
| Field | Description | Handler Method |
|---|---|---|
messages | Fired when a new message is received (text, media, location, etc.). | handleIncomingMessage |
statuses | Fired when the status of an outbound message changes (e.g., sent, delivered, read). | handleMessageStatus |
message_template_status_update | Fired when a message template's approval status changes. | handleTemplateStatusUpdate |
Sources: app/Http/Controllers/Api/WhatsAppWebhookController.php:91-143
Template Status Updates
When a template's status is updated in the Meta Business Manager (e.g., APPROVED, REJECTED), a webhook is sent. The handleTemplateStatusUpdate method finds the corresponding WhatsAppTemplate in the local database by template_id or name and updates its status field. This ensures the application has the latest status for each template.
Sources: app/Http/Controllers/Api/WhatsAppWebhookController.php:483-568, app/Services/TemplateService.php:403-481
Polar.sh Webhook
The Polar.sh webhook is used to manage customer subscriptions. It notifies the application about events like subscription creation, updates, and cancellations.
Sources: app/Http/Controllers/Api/PolarWebhookController.php
Security: Signature Validation
All incoming webhooks from Polar.sh are secured with a signature. The PolarWebhookController uses the PolarService to validate the webhook-signature header.
The validation process is as follows:
- The webhook secret is retrieved from the
polar.webhook_secretconfiguration. If it's missing, validation fails. - The
webhook-signatureheader is parsed to extract the timestamp (t) and the signature (v1). - A new signature is computed by creating an HMAC-SHA256 hash of the payload, prefixed with the timestamp and a dot (e.g.,
timestamp.payload). - The computed signature is compared with the signature from the header. If they match, the webhook is considered authentic.
This flow is tested in PolarWebhookControllerTest and PolarServicePropertyTest.
Sources: app/Http/Controllers/Api/PolarWebhookController.php:39-51, tests/Feature/PolarWebhookControllerTest.php:31-40, tests/Unit/Services/PolarServicePropertyTest.php:31-52
This diagram shows the signature validation logic.
Event Handling
The handle method in PolarWebhookController processes valid webhooks. It inspects the type field of the payload and uses a switch statement to delegate to specific service methods.
| Event Type | Description | Service Method |
|---|---|---|
subscription.created | A new subscription has been created. | createSubscription |
subscription.updated | An existing subscription has been updated (e.g., plan change, renewal). | updateSubscription |
subscription.cancelled | A subscription has been cancelled. | cancelSubscription |
Sources: app/Http/Controllers/Api/PolarWebhookController.php:53-85, tests/Feature/PolarWebhookControllerTest.php:101-137
Testing
The repository includes scripts for testing webhook functionality.
test_webhook_message.php: A command-line script that simulates a webhook from Meta for an incoming message. It finds an active WhatsApp account, constructs a valid payload, and sends it to the local/api/whatsapp/webhookendpoint. It then checks the database to verify that the contact and message were created correctly.
Sources: test_webhook_message.phptests/Feature/PolarWebhookControllerTest.php: This feature test covers the Polar.sh webhook endpoint. It tests for rejection of invalid signatures and successful creation/cancellation of subscriptions based on valid signed payloads.
Sources: tests/Feature/PolarWebhookControllerTest.phptests/Unit/Controllers/WhatsAppWebhookControllerPropertyTest.php: These property-based tests ensure the correctness of multi-tenant webhook routing and data isolation between users.
Sources: tests/Unit/Controllers/WhatsAppWebhookControllerPropertyTest.php