Skip to content
View as Markdown

SMS Campaign Hooks

FluentCRM Pro Intermediate

These action hooks fire during SMS campaign lifecycle events, message sending, delivery tracking, and subscriber opt-in/out. All SMS hooks require FluentCRM Pro.

Provider Registration

fluent_crm/register_sms_providers

Fires once while the SMS module boots, after the built-in Twilio and AWS drivers are registered. Instantiate your own driver here — AbstractSMSDriver's constructor registers itself with SMSDriverManager, which is what feeds the provider dropdown and settings form.

This runs before the module's isActive() check, so the settings screen always lists your driver even when SMS is not configured yet.

Parameters

None.

Usage:

php
use FluentCampaign\App\Modules\SMS\Providers\AbstractSMSDriver;

class MySMSDriver extends AbstractSMSDriver
{
    public function getSlug(): string
    {
        return 'my_sms_service';
    }

    public function getLabel(): string
    {
        return 'My SMS Service';
    }

    public function getFields(): array
    {
        return [
            'api_key'    => ['type' => 'text', 'label' => 'API Key'],
            'api_secret' => ['type' => 'password', 'label' => 'API Secret'],
        ];
    }

    public function send(string $to, string $message, array $settings): array
    {
        // Return ['status' => 'success', 'provider_message_id' => '...'] on success,
        // or ['status' => 'error', 'message' => '...'] on failure — the scheduler
        // checks $result['status'] === 'success' to mark the message sent.
    }
}

add_action('fluent_crm/register_sms_providers', function() {
    new MySMSDriver();
});

Source: fluentcampaign-pro/app/Modules/SMS/SMSModule.php


fluent_crm/register_whatsapp_providers

WhatsApp counterpart of fluent_crm/register_sms_providers. Fires while the SMS module boots, after the built-in WhatsApp drivers are registered. Instantiate your own driver here — AbstractWhatsAppDriver's constructor registers itself with WhatsAppDriverManager, which is what feeds the WhatsApp provider dropdown and settings form.

WhatsApp channel status

The WhatsApp channel is feature-flagged off in current Pro builds — the do_action() call in SMSModule::register() is commented out and WhatsAppHelper::isActive() returns false — so this hook does not fire yet. It is documented here as the registration point for third-party drivers once the channel is enabled.

Parameters

None.

Usage:

php
use FluentCampaign\App\Modules\SMS\Providers\AbstractWhatsAppDriver;

class MyWhatsAppDriver extends AbstractWhatsAppDriver
{
    public function getSlug(): string
    {
        return 'my_whatsapp_service';
    }

    public function getLabel(): string
    {
        return 'My WhatsApp Service';
    }

    public function getFields(): array
    {
        return [
            'api_key'    => ['type' => 'text', 'label' => 'API Key', 'required' => true, 'default' => ''],
            'api_secret' => ['type' => 'password', 'label' => 'API Secret', 'required' => true, 'default' => ''],
        ];
    }

    public function send(string $to, string $message, array $settings): array
    {
        // Return ['status' => 'success', 'status_code' => 200, 'message' => '...',
        // 'response' => $response, 'provider_message_id' => '...'] on success,
        // or ['status' => 'error', 'message' => '...'] on failure — the scheduler
        // checks $result['status'] === 'success' to mark the message sent.
    }
}

add_action('fluent_crm/register_whatsapp_providers', function() {
    new MyWhatsAppDriver();
});

Source: fluentcampaign-pro/app/Modules/SMS/SMSModule.php


Campaign Lifecycle

fluent_crm/sms_campaign_created

Fires when a new SMS campaign is created.

Parameters

  • $campaign SMSCampaign Model

Usage:

php
add_action('fluent_crm/sms_campaign_created', function($campaign) {
    // New SMS campaign created
});

Source: fluentcampaign-pro/app/Modules/SMS/Http/Controllers/SMSController.php


fluent_crm/sms_campaign_updated

Fires when an SMS campaign is updated.

Parameters

  • $campaign SMSCampaign Model

Usage:

php
add_action('fluent_crm/sms_campaign_updated', function($campaign) {
    // SMS campaign was modified
});

Source: fluentcampaign-pro/app/Modules/SMS/Http/Controllers/SMSController.php


fluent_crm/sms_campaign_status_active

Fires at the start of SMSController::schedule(), once the campaign has passed the "must still be a draft" guard but before any status change is written.

WARNING

The campaign passed to this hook still has status = 'draft'. Re-read the model if you need the post-schedule status.

Parameters

  • $smsCampaign SMSCampaign Model - still in draft status at this point

Usage:

php
add_action('fluent_crm/sms_campaign_status_active', function($smsCampaign) {
    // SMS campaign activated
});

Source: fluentcampaign-pro/app/Modules/SMS/Http/Controllers/SMSController.php


fluent_crm/sms_campaign_scheduled

Fires when an SMS campaign is scheduled for future sending. It does not fire for a send-immediately campaign — that path schedules the batch-generation job directly instead.

Parameters

  • $smsCampaign SMSCampaign Model - freshly re-read from the database, so its status and scheduled_at are the saved values
  • $scheduledAt String - the campaign's scheduled_at column, a site-local Y-m-d H:i:s datetime (not a Unix timestamp)

Usage:

php
add_action('fluent_crm/sms_campaign_scheduled', function($smsCampaign, $scheduledAt) {
    // $scheduledAt is a MySQL datetime string, e.g. '2026-08-12 09:30:00'
    $timestamp = strtotime($scheduledAt);
}, 10, 2);

Source: fluentcampaign-pro/app/Modules/SMS/Http/Controllers/SMSController.php


fluent_crm/sms_campaign_processing_start

Fires when a pending-scheduled campaign flips to processing — that is, when its scheduled time is less than six minutes away and the admin screen polls for processing stats. The campaign has already been saved with status = 'processing' and recipients_count = 0 when this runs.

Parameters

  • $campaign SMSCampaign Model - already saved as processing

Usage:

php
add_action('fluent_crm/sms_campaign_processing_start', function($campaign) {
    // SMS campaign processing started
});

Source: fluentcampaign-pro/app/Modules/SMS/Http/Controllers/SMSController.php


fluent_crm/sms_campaign_duplicated

Fires when an SMS campaign is duplicated.

Parameters

  • $newCampaign SMSCampaign Model - the new copy, created as a draft with a [Duplicate] title prefix and the original's labels already attached
  • $oldCampaign SMSCampaign Model - the original

Usage:

php
add_action('fluent_crm/sms_campaign_duplicated', function($newCampaign, $oldCampaign) {
    // SMS campaign was duplicated
}, 10, 2);

Source: fluentcampaign-pro/app/Modules/SMS/Http/Controllers/SMSController.php


fluent_crm/sms_campaign_archived

Fires when an SMS campaign is archived — either because a recurring/auto-processing campaign has no future runs left, or by the cleanup cron when a finished campaign has no unsent messages remaining. The campaign row is saved as status = 'archived' in all cases, but only the first two call sites re-read the model before firing; on the cron path the model still carries its pre-archive status.

Parameters

  • $smsCampaign SMSCampaign Model - the row is saved as archived, though on the cron path the passed instance may still show the previous status

Usage:

php
add_action('fluent_crm/sms_campaign_archived', function($smsCampaign) {
    // SMS campaign archived
});

Source: fluentcampaign-pro/app/Modules/SMS/Http/Controllers/SMSController.php, fluentcampaign-pro/app/Modules/SMS/SMSScheduler.php


fluent_crm/sms_campaign_deleted

Fires after an SMS campaign is permanently deleted, both from the single-delete endpoint and once per campaign from the delete_campaigns bulk action.

WARNING

The campaign row and its message/meta data are already gone when this fires — only the ID is passed. Capture anything you need on fluent_crm/sms_campaign_updated instead.

Parameters

  • $campaignId INT - deleted campaign ID

Usage:

php
add_action('fluent_crm/sms_campaign_deleted', function($campaignId) {
    // SMS campaign deleted
});

Source: fluentcampaign-pro/app/Modules/SMS/Http/Controllers/SMSController.php


Sending & Delivery

fluent_crm/sms_sent

Fires after an SMS message is successfully sent, once the message row has been marked sent and the campaign's sent_count incremented.

Parameters

  • $smsMessage SMSMessage Model - the pre-update instance, so its status still reflects the value from before the send was recorded
  • $result Array - the driver's response; carries provider_message_id when the provider returns one

Usage:

php
add_action('fluent_crm/sms_sent', function($smsMessage, $result) {
    // SMS sent successfully
    // $result contains provider-specific response data
}, 10, 2);

Source: fluentcampaign-pro/app/Modules/SMS/SMSScheduler.php


fluent_crm/sms_failed

Fires after an SMS message fails to send, once the message row has been marked failed and the campaign's failed_count incremented.

Parameters

  • $smsMessage SMSMessage Model - the pre-update instance, so its status still reflects the value from before the failure was recorded
  • $errorMessage String - error message from the provider; also stored on the message's notes column

Usage:

php
add_action('fluent_crm/sms_failed', function($smsMessage, $errorMessage) {
    // SMS failed - log or retry
    error_log('SMS failed: ' . $errorMessage);
}, 10, 2);

Source: fluentcampaign-pro/app/Modules/SMS/SMSScheduler.php


fluent_crm/whatsapp_sent

WhatsApp counterpart of fluent_crm/sms_sent — messages route here when their channel is whatsapp. Fires after a WhatsApp message is successfully sent, once the message row has been marked sent and the campaign's sent_count incremented. Note it fires in addition to the generic fluent_crm/sms_sent (which runs for every channel), not instead of it — the same applies to whatsapp_failed and sms_failed.

Parameters

  • $smsMessage SMSMessage Model - the pre-update instance, so its status still reflects the value from before the send was recorded
  • $result Array - the WhatsApp driver's response (status, status_code, message, response); carries provider_message_id when the provider returns one

Usage:

php
add_action('fluent_crm/whatsapp_sent', function($smsMessage, $result) {
    // WhatsApp message sent successfully
    // $result contains provider-specific response data
}, 10, 2);

Source: fluentcampaign-pro/app/Modules/SMS/SMSScheduler.php


fluent_crm/whatsapp_failed

WhatsApp counterpart of fluent_crm/sms_failed. Fires after a WhatsApp message fails to send, once the message row has been marked failed and the campaign's failed_count incremented. Pre-send guards that mark a message failed without attempting a send — for example a contact whose whatsapp_status is not whatsapp_subscribed — do not fire this hook.

Parameters

  • $smsMessage SMSMessage Model - the pre-update instance, so its status still reflects the value from before the failure was recorded
  • $errorMessage String - the driver response's message, or Unknown error when it carries none; also stored on the message's notes column

Usage:

php
add_action('fluent_crm/whatsapp_failed', function($smsMessage, $errorMessage) {
    // WhatsApp send failed - log or retry
    error_log('WhatsApp failed: ' . $errorMessage);
}, 10, 2);

Source: fluentcampaign-pro/app/Modules/SMS/SMSScheduler.php


Opt-in & Opt-out

fluent_crm/contact_sms_subscribed

Fires when an inbound message opts a contact in to SMS. The contact is matched by phone, so nothing fires for an unknown number. The contact's sms_status is already saved as sms_subscribed.

Parameters

  • $subscriber Subscriber Model - already saved with sms_status = 'sms_subscribed'
  • $data Array - the inbound webhook context; includes a provider key

Usage:

php
add_action('fluent_crm/contact_sms_subscribed', function($subscriber, $data) {
    // Contact opted in to SMS
}, 10, 2);

Source: fluentcampaign-pro/app/Modules/SMS/SMSHelper.php


fluent_crm/contact_sms_unsubscribed

Fires when an inbound message opts a contact out of SMS. The contact is matched by phone, so nothing fires for an unknown number. The contact's sms_status is already saved as sms_unsubscribed.

Parameters

  • $subscriber Subscriber Model - already saved with sms_status = 'sms_unsubscribed'
  • $data Array - the inbound webhook context; includes a provider key

Usage:

php
add_action('fluent_crm/contact_sms_unsubscribed', function($subscriber, $data) {
    // Contact opted out of SMS
}, 10, 2);

Source: fluentcampaign-pro/app/Modules/SMS/SMSHelper.php


fluent_crm/contact_whatsapp_subscribed

WhatsApp counterpart of fluent_crm/contact_sms_subscribed. Fires when an inbound WhatsApp message — a start/subscribe keyword arriving on the Twilio WhatsApp or Meta Cloud webhook — opts a contact in to WhatsApp. The contact is matched by phone, so nothing fires for an unknown number. The contact's whatsapp_status is already saved as whatsapp_subscribed.

Parameters

  • $subscriber Subscriber Model - already saved with whatsapp_status = 'whatsapp_subscribed'
  • $data Array - the inbound webhook context; includes a provider key (twilio_whatsapp or meta_cloud)

Usage:

php
add_action('fluent_crm/contact_whatsapp_subscribed', function($subscriber, $data) {
    // Contact opted in to WhatsApp
}, 10, 2);

Source: fluentcampaign-pro/app/Modules/SMS/WhatsAppHelper.php


fluent_crm/contact_whatsapp_unsubscribed

WhatsApp counterpart of fluent_crm/contact_sms_unsubscribed. Fires when an inbound WhatsApp message — a stop/cancel/unsubscribe keyword arriving on the Twilio WhatsApp or Meta Cloud webhook — opts a contact out of WhatsApp. The contact is matched by phone, so nothing fires for an unknown number. The contact's whatsapp_status is already saved as whatsapp_unsubscribed.

Parameters

  • $subscriber Subscriber Model - already saved with whatsapp_status = 'whatsapp_unsubscribed'
  • $data Array - the inbound webhook context; includes a provider key (twilio_whatsapp or meta_cloud)

Usage:

php
add_action('fluent_crm/contact_whatsapp_unsubscribed', function($subscriber, $data) {
    // Contact opted out of WhatsApp
}, 10, 2);

Source: fluentcampaign-pro/app/Modules/SMS/WhatsAppHelper.php


Provider Webhooks

fluent_crm_sms_custom_provider_webhook

Generic webhook hook for custom SMS providers. Fires when an incoming webhook is received for a provider that has no built-in handler. The signature check has already passed and the payload is sanitized by the time this runs.

WARNING

Built-in providers never reach this hook. twilio, twilio_whatsapp and meta_cloud are routed to their own handlers in SMSHandler, so only providers registered through fluent_crm/register_sms_providers fire it.

Parameters

  • $bodyData Array - the sanitized webhook request body
  • $provider String - provider slug taken from the webhook URL

Usage:

php
add_action('fluent_crm_sms_custom_provider_webhook', function($bodyData, $provider) {
    if ($provider === 'my_sms_service') {
        // Handle delivery receipt, status update, etc.
    }
}, 10, 2);

Source: fluentcampaign-pro/app/Modules/SMS/SMSReceiver.php


fluent_crm_sms_{$provider}_webhook

Provider-specific webhook hook, fired immediately after fluent_crm_sms_custom_provider_webhook. The hook name includes the provider slug — for a driver whose getSlug() returns my_sms_service, the hook is fluent_crm_sms_my_sms_service_webhook.

WARNING

Like the generic hook above, this only fires for custom providers. There is no fluent_crm_sms_twilio_webhook — Twilio, Twilio WhatsApp and Meta Cloud are handled by their built-in SMSReceiver methods and never reach this dispatcher.

Parameters

  • $bodyData Array - the sanitized webhook request body

Usage:

php
add_action('fluent_crm_sms_my_sms_service_webhook', function($bodyData) {
    // Handle a delivery receipt or inbound reply from your own provider
});

Source: fluentcampaign-pro/app/Modules/SMS/SMSReceiver.php