Skip to content

Set Up Webhooks ​

Learn how to configure webhooks to receive real-time notifications about all platform events directly in your system.

Quick Setup ​

  1. Navigate to Settings → Webhook in your dashboard
  2. Enter your HTTPS webhook URL (e.g., https://yourapp.com/api/cubeconnect/webhook)
  3. Click Generate new secret to create a signing secret
  4. Click Save Settings
  5. 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 ​

EventWhen
message.status_updatedOutbound message status changes (sent, delivered, read, failed)
message.receivedCustomer sends a message to your WhatsApp number

Campaigns ​

EventWhen
campaign.createdNew campaign is created
campaign.startedScheduled campaign begins execution
campaign.completedAll campaign messages processed (includes sent/failed counts)

Templates ​

EventWhen
template.submittedNew template submitted to Meta for approval
template.status_changedTemplate approved, rejected, paused, or disabled by Meta

Chatbot ​

EventWhen
flow.session_startedCustomer triggers a chatbot flow
flow.session_completedChatbot flow reaches the end
flow.session_cancelledCustomer cancels (sends "cancel", "exit", etc.)

Quality ​

EventWhen
account.quality_eventRecipient 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 Monitor

Delivery Details ​

SettingValue
HTTP MethodPOST
Content-Typeapplication/json
Timeout10 seconds
Max Retries3
Retry Backoff10 seconds
User-AgentCubeConnect-Webhook/1.0

Best Practices ​

  1. Return 200 quickly — Process events asynchronously if possible. CubeConnect considers any 2xx response as success.
  2. Verify signatures — Always verify the X-Webhook-Signature header to ensure requests come from CubeConnect.
  3. Handle duplicates — In rare cases, the same event might be delivered more than once. Use the event + timestamp to deduplicate.
  4. 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

CubeConnect WhatsApp Business Platform