Developer Guide: Hooks and Building Channel Add-ons
Super Speedy Chat is built so that new chat channels (WhatsApp, Telegram, SMS, …) can be added as separate plugins without touching core. The bundled Discord integration is itself written entirely against this extension surface — it’s the reference implementation. This guide documents the hooks, helpers, and registry you build against.
For the big-picture component map and the message lifecycle, read the Architecture Overview first. This guide is the practical hook reference.
Table of Contents
The model in one paragraph
A conversation has many participants (typed visitor / admin / bot / system) and many messages, and carries a channel discriminator. Core never calls a channel directly — it fires lifecycle actions, and channels listen. To push messages in from an external channel, you call two SSC_Chat helpers. To appear in settings/REST/UI, you hook the registration filters/actions. That’s the whole API.
Lifecycle actions (core fires, you listen)
| Action | Args | Fires when |
|---|---|---|
ssc_visitor_message_sent |
$message_id, $conversation, $message_text, $participant |
A visitor sends a message. |
ssc_admin_reply_sent |
$message_id, $conversation, $message_text, $admin_user_id |
An admin replies (from wp-admin; $admin_user_id is null for replies that originated in an external channel). |
ssc_bot_message_sent |
$message_id, $conversation, $message_text, $message_type |
An auto/bot message is sent. |
ssc_conversation_status_changed |
$conversation_id, $new_status, $old_status |
Status transitions (active/waiting/closed/archived). |
ssc_conversation_created |
$conversation |
A new conversation row is created. |
Example — relay every visitor message to your channel:
add_action( 'ssc_visitor_message_sent', function ( $message_id, $conversation, $text, $participant ) {
if ( $conversation->channel !== 'mychannel' ) {
return; // not ours
}
My_Channel::push( $conversation, $text );
}, 10, 4 );
Inbound helpers (you call core)
When a message arrives from your channel, don’t write to the tables yourself — use these so participant creation, status transitions, and lifecycle hooks all happen correctly:
SSC_Chat::get_or_create_external_conversation( $args )— resolves (or creates) a conversation owned by an external channel. There’s no browser cookie, so identity is derived assha256('ext:' . $channel . ':' . $external_id).php $conv = SSC_Chat::get_or_create_external_conversation( array( 'channel' => 'mychannel', 'external_id' => $sender_phone_or_id, // stable per remote user 'display_name' => $sender_name, 'metadata' => array( 'phone' => $sender_phone ), ) );SSC_Chat::external_inbound( $args )— appends an inbound message and fires the right lifecycle hook.php SSC_Chat::external_inbound( array( 'conversation_id' => $conv->id, 'channel' => 'mychannel', 'author_name' => $sender_name, 'author_type' => 'visitor', // or 'admin' / 'bot' / 'system' 'message' => $text, 'external_msg_id' => $remote_id, // optional, for your dedup ) );
Registration hooks (appear in settings, REST, UI)
| Hook | Type | Use it to |
|---|---|---|
ssc_register_rest_routes |
action ($rest) |
Register your own REST routes (e.g. an inbound webhook). |
ssc_register_settings |
action | Add Settings API sections/fields. |
ssc_settings_tabs |
filter ($tabs) |
Add a settings tab (['label'=>…, 'order'=>…]). |
ssc_sanitize_options |
filter ($sanitized, $input) |
Sanitise your own keys in the shared ssc_options array. |
ssc_channels |
filter ($channels) |
Declare your channel (['id'=>…, 'label'=>…, 'icon'=>…]). |
ssc_frontend_config |
filter ($config) |
Add keys to the front-end ssc_config blob. |
ssc_enqueue_frontend |
action ($ultra_ajax) |
Enqueue your own bubble JS after core’s. |
ssc_conversation_sidebar |
action ($conversation) |
Render an extra panel in the admin conversation sidebar. |
ssc_conversation_reply_footer |
action ($conversation) |
Render notices below the admin reply box. |
Filters worth knowing
ssc_llm_daily_cap— integer site-wide daily ceiling on LLM classification calls (return0to disable the cap).- Front-end
window.ssc.hooks— a tiny@wordpress/hooks-style API in the bubble: - actions:
ssc.bubble.rendered,ssc.bubble.opened,ssc.bubble.closed,ssc.bubble.messageSent,ssc.bubble.messageReceived - filter:
ssc.bubble.welcomeMessagejs window.ssc.hooks.addFilter('ssc.bubble.welcomeMessage', 'mychannel', function (msg) { return msg || 'Hi there!'; });
Registering your add-on
Call SSC_Addons::register() on plugins_loaded (priority ≥ 20) so core can list you on the Status tab and gate on version compatibility:
add_action( 'plugins_loaded', function () {
SSC_Addons::register( array(
'slug' => 'ssc-mychannel',
'name' => 'My Channel for Super Speedy Chat',
'version' => '1.0.0',
'channel' => 'mychannel',
'requires_core' => '1.08',
'requires_addon_api' => '1.0',
'plugin_file' => __FILE__,
) );
}, 20 );
If your requires_addon_api is newer than core’s SSC_Addons::ADDON_API_VERSION, registration is refused and an admin notice is queued.
A channel add-on, end to end
- On
plugins_loaded(≥20):SSC_Addons::register()and add yourssc_channelsentry. - Add settings via
ssc_settings_tabs+ssc_register_settings+ssc_sanitize_options. - Register an inbound webhook via
ssc_register_rest_routes; in its handler, callget_or_create_external_conversation()thenexternal_inbound(). - Listen on
ssc_admin_reply_sent(andssc_visitor_message_sentif you mirror both ways) to push outbound messages to your channel. - Optionally add sidebar/footer panels and front-end behaviour.
Read includes/class-ssc-discord.php as the worked example — every step above is visible there.