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