💎 ChatSDK Ruby

Building Adapters

Guide to creating a custom platform adapter for ChatSDK by subclassing Adapter::Base.

This guide walks you through creating a custom platform adapter for ChatSDK.

Subclass Adapter::Base

Create a new class that inherits from ChatSDK::Adapter::Base:

module ChatSDK
  module MyPlatform
    class Adapter < ChatSDK::Adapter::Base
      # Declare which capabilities your adapter supports
      capabilities :edit_messages, :delete_messages, :threads, :direct_messages

      def initialize(api_key:)
        @api_key = api_key
        @client = MyPlatform::Client.new(api_key: api_key)
      end

      def name
        :my_platform
      end

      def client
        @client
      end

      # --- Inbound ---

      def verify_request!(rack_request)
        # Verify the webhook signature
        signature = rack_request.env["HTTP_X_SIGNATURE"]
        body = rack_request.body.read
        rack_request.body.rewind

        unless valid_signature?(body, signature)
          raise ChatSDK::SignatureVerificationError, "Invalid signature"
        end

        true
      end

      def ack_response(rack_request)
        # Return a Rack response tuple if the platform requires
        # an immediate acknowledgment, or nil for the default 200.
        nil
      end

      def parse_events(rack_request)
        body = rack_request.body.read
        rack_request.body.rewind
        payload = JSON.parse(body)

        # Parse the platform payload into ChatSDK events
        case payload["type"]
        when "message"
          [build_mention_event(payload)]
        when "reaction"
          [build_reaction_event(payload)]
        else
          []
        end
      end

      # --- Outbound ---

      def post_message(channel_id:, message:, thread_id: nil)
        msg = ChatSDK::PostableMessage.from(message)
        result = @client.send(
          channel: channel_id,
          text: msg.text,
          thread: thread_id
        )

        ChatSDK::Message.new(
          id: result["id"],
          text: msg.text || "",
          author: ChatSDK::Author.new(id: "bot", name: "bot", platform: name, bot: true),
          thread_id: thread_id || result["id"],
          channel_id: channel_id,
          platform: name,
          raw: result
        )
      end

      def edit_message(channel_id:, message_id:, message:)
        require_capability!(:edit_messages)
        msg = ChatSDK::PostableMessage.from(message)
        @client.update(channel: channel_id, id: message_id, text: msg.text)
      end

      def delete_message(channel_id:, message_id:)
        require_capability!(:delete_messages)
        @client.delete(channel: channel_id, id: message_id)
      end

      def mention(user_id)
        "@#{user_id}"
      end

      def render(postable_message)
        # Convert cards to your platform's format
        if postable_message.card?
          MyCardRenderer.new.render(postable_message.card)
        else
          postable_message.text
        end
      end

      private

      def valid_signature?(body, signature)
        # Your signature verification logic
        true
      end

      def build_mention_event(payload)
        author = ChatSDK::Author.new(
          id: payload["user_id"],
          name: payload["user_name"],
          platform: name
        )
        message = ChatSDK::Message.new(
          id: payload["message_id"],
          text: payload["text"],
          author: author,
          thread_id: payload["thread_id"],
          channel_id: payload["channel_id"],
          platform: name,
          raw: payload
        )
        ChatSDK::Events::Mention.new(
          message: message,
          thread_id: payload["thread_id"],
          channel_id: payload["channel_id"],
          platform: name,
          adapter_name: name
        )
      end
    end
  end
end

Required Methods

Every adapter must implement these methods:

MethodDescription
nameReturn a Symbol identifying this adapter
clientReturn the underlying API client
verify_request!(rack_request)Verify webhook signature; raise SignatureVerificationError on failure
parse_events(rack_request)Parse a webhook payload into an array of Events::Base subclasses
post_message(channel_id:, message:, thread_id:)Send a message; return a Message
mention(user_id)Format a user mention string
render(postable_message)Convert a PostableMessage to platform-native format

Optional Methods

Most methods have default implementations that raise NotSupportedError. Override them and declare the matching capability:

MethodCapability
edit_message:edit_messages
delete_message:delete_messages
post_ephemeral:ephemeral_messages
upload_file:file_uploads
add_reaction / remove_reaction:reactions
open_dm:direct_messages
fetch_messages:message_history
open_modal:modals
start_typing:typing_indicator

The optional stream(channel_id:, thread_id:, stream:, options: {}) hook is different: its base implementation returns nil, which delegates to ChatSDK's post-and-edit fallback. Implement it only when the platform has a native streaming API. Return a ChatSDK::Message after consuming the source when native streaming succeeds; return nil before consuming it when native streaming is unavailable. Existing adapters do not need to implement this hook.

Adapters that need persistent state can implement set_state(state). ChatSDK::Chat calls it during construction with its configured state adapter.

Declaring Capabilities

Use the capabilities class method to declare what your adapter supports. Only declare capabilities for methods you actually implement:

capabilities :edit_messages, :delete_messages, :reactions

If a user calls a method gated by a capability you have not declared, require_capability! raises NotSupportedError.

Event Types

Your parse_events method should return instances of:

Event ClassType SymbolUsed By
Events::Mention:mentionon_new_mention, on_new_message
Events::SubscribedMessage:subscribed_messageon_subscribed_message
Events::DirectMessage:direct_messageon_direct_message
Events::Reaction:reactionon_reaction
Events::Action:actionon_action
Events::SlashCommand:slash_commandon_slash_command

Gem Structure

Organize your adapter as a separate gem:

chat_sdk-my_platform/
  lib/
    chat_sdk/
      my_platform.rb
      my_platform/
        adapter.rb
        event_parser.rb
        card_renderer.rb
  chat_sdk-my_platform.gemspec
  spec/

Verifying Your Adapter

Use the shared contract examples to verify your adapter implements the required interface:

require "chat_sdk/testing/adapter_contract"

RSpec.describe ChatSDK::MyPlatform::Adapter do
  subject { described_class.new(api_key: "test") }
  it_behaves_like "a chat_sdk platform adapter"
end

See Testing Adapters for more testing guidance.

On this page