Appearance
Set Up Webhooks
Learn how to configure webhooks to receive real-time notifications about all platform events directly in your system.
Quick Setup
- Navigate to Settings → Webhook in your dashboard
- Enter your HTTPS webhook URL (e.g.,
https://yourapp.com/api/cubeconnect/webhook) - Click Generate new secret to create a signing secret
- Click Save Settings
- Click Send Test to verify the connection
TIP
Use webhook.site during development to inspect incoming webhook payloads.
What Events You'll Receive
Once configured, your webhook URL will receive POST requests for all platform events:
Messages
| Event | When |
|---|---|
message.status_updated | Outbound message status changes (sent, delivered, read, failed) |
message.received | Customer sends a message to your WhatsApp number |
Campaigns
| Event | When |
|---|---|
campaign.created | New campaign is created |
campaign.started | Scheduled campaign begins execution |
campaign.completed | All campaign messages processed (includes sent/failed counts) |
Templates
| Event | When |
|---|---|
template.submitted | New template submitted to Meta for approval |
template.status_changed | Template approved, rejected, paused, or disabled by Meta |
Chatbot
| Event | When |
|---|---|
flow.session_started | Customer triggers a chatbot flow |
flow.session_completed | Chatbot flow reaches the end |
flow.session_cancelled | Customer cancels (sends "cancel", "exit", etc.) |
Quality
| Event | When |
|---|---|
account.quality_event | Recipient blocks or reports your messages |
See Webhook Events for full payload examples.
Building a Webhook Endpoint
Basic Example (PHP / Laravel)
php
// routes/api.php
Route::post('/cubeconnect/webhook', [WebhookController::class, 'handle']);
// app/Http/Controllers/WebhookController.php
class WebhookController extends Controller
{
public function handle(Request $request)
{
// 1. Verify signature
$signature = $request->header('X-Webhook-Signature');
$timestamp = $request->header('X-Webhook-Timestamp');
$secret = config('services.cubeconnect.webhook_secret');
if ($signature && $secret) {
$expected = hash_hmac('sha256', $timestamp . '.' . $request->getContent(), $secret);
if (!hash_equals($expected, $signature)) {
return response('Invalid signature', 401);
}
}
// 2. Process event
$event = $request->input('event');
match ($event) {
'message.received' => $this->handleIncomingMessage($request->all()),
'message.status_updated' => $this->handleStatusUpdate($request->all()),
'campaign.completed' => $this->handleCampaignCompleted($request->all()),
'template.status_changed' => $this->handleTemplateStatus($request->all()),
'flow.session_completed' => $this->handleFlowCompleted($request->all()),
'account.quality_event' => $this->handleQualityEvent($request->all()),
default => null,
};
return response('OK', 200);
}
private function handleIncomingMessage(array $data)
{
// New message from customer
Log::info('New message from ' . $data['from'] . ': ' . $data['content']);
}
private function handleStatusUpdate(array $data)
{
// Update your local message record
YourMessage::where('wa_message_id', $data['message_id'])
->update(['status' => $data['status']]);
}
private function handleCampaignCompleted(array $data)
{
// Campaign finished - notify your team
Log::info("Campaign '{$data['name']}' completed: {$data['sent_count']} sent, {$data['failed_count']} failed");
}
private function handleTemplateStatus(array $data)
{
// Template approved or rejected
if ($data['status'] === 'rejected') {
// Alert team about rejected template
}
}
private function handleFlowCompleted(array $data)
{
// Chatbot flow finished for customer
Log::info("Flow completed for {$data['customer_phone']}");
}
private function handleQualityEvent(array $data)
{
// Quality alert - block or report
Log::warning("Quality event: {$data['type']} from {$data['user_phone']}");
}
}Basic Example (Node.js / Express)
javascript
const express = require('express')
const crypto = require('crypto')
const app = express()
app.use(express.json())
app.post('/cubeconnect/webhook', (req, res) => {
// 1. Verify signature
const signature = req.headers['x-webhook-signature']
const timestamp = req.headers['x-webhook-timestamp']
const secret = process.env.CUBECONNECT_WEBHOOK_SECRET
if (signature && secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(timestamp + '.' + JSON.stringify(req.body))
.digest('hex')
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) {
return res.status(401).send('Invalid signature')
}
}
// 2. Process event
const { event } = req.body
switch (event) {
case 'message.received':
console.log(`New message from ${req.body.from}: ${req.body.content}`)
break
case 'campaign.completed':
console.log(`Campaign "${req.body.name}": ${req.body.sent_count} sent, ${req.body.failed_count} failed`)
break
case 'template.status_changed':
console.log(`Template "${req.body.template_name}" is now ${req.body.status}`)
break
case 'account.quality_event':
console.warn(`Quality ${req.body.type} from ${req.body.user_phone}`)
break
}
res.sendStatus(200)
})How CubeConnect Delivers Webhooks
Event occurs on platform
↓
CubeConnect queues webhook delivery (non-blocking)
↓
POST to your webhook URL
↓
If fails → retry up to 3 times (10s backoff)
↓
If all retries fail → logged in Webhook Health MonitorDelivery Details
| Setting | Value |
|---|---|
| HTTP Method | POST |
| Content-Type | application/json |
| Timeout | 10 seconds |
| Max Retries | 3 |
| Retry Backoff | 10 seconds |
| User-Agent | CubeConnect-Webhook/1.0 |
Best Practices
- Return 200 quickly — Process events asynchronously if possible. CubeConnect considers any 2xx response as success.
- Verify signatures — Always verify the
X-Webhook-Signatureheader to ensure requests come from CubeConnect. - Handle duplicates — In rare cases, the same event might be delivered more than once. Use the
event+timestampto deduplicate. - Use HTTPS — Always use an HTTPS URL for production webhooks.
Webhook Health Monitor
Monitor your webhook delivery status at Settings → Webhook → Webhook Health Monitor in your dashboard. This shows:
- Recent webhook delivery attempts
- Success/failure rates
- Error messages for failed deliveries
Troubleshooting
Webhooks not arriving
- Verify your webhook URL is correct and publicly accessible
- Check that your server returns a 2xx status within 10 seconds
- Look at the Webhook Health Monitor for error details
Invalid signature errors
- Make sure you're using the raw request body (not parsed JSON) for signature verification
- Verify your signing secret matches the one in Settings → Webhook
- Check that the timestamp difference is within 5 minutes
Duplicate events
- CubeConnect may retry failed deliveries, which can cause duplicates
- Use idempotent handlers or track processed event timestamps