> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs.pinnacle.sh/v-2/quickstart/rcs/ruby/receive/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.pinnacle.sh/_mcp/server. # Receiving RCS Messages > Receiving your first RCS message with Pinnacle's Ruby SDK ```ruby require 'sinatra' require 'json' require 'dotenv/load' require 'rcs' set :port, ENV['PORT'] || 4567 set :bind, '0.0.0.0' # Disable host authorization to allow webhooks from external services set :host_authorization, { permitted_hosts: [] } # Initialize Pinnacle RCS client client = Pinnacle::Client.new(api_key: ENV["PINNACLE_API_KEY"]) # RCS webhook endpoint post '/inbound-rcs' do content_type :json # Convert Sinatra request to the format expected by the Pinnacle SDK req_hash = { headers: request.env.select { |k, v| k.start_with?("HTTP_") } .transform_keys { |k| k.sub(/^HTTP_/, "").split("_").map(&:capitalize).join("-") }, body: request.body.read } # Process and validate the webhook # Returns a fully typed MessageEvent or UserEvent object message_event = client.messages.process( req_hash, secret: ENV["PINNACLE_SIGNING_SECRET"] ) puts "\n=== Received RCS Webhook ===" puts "Event Type: #{message_event.type}" puts "==========================\n" # Process the message based on event type case message_event.type when 'MESSAGE.RECEIVED' handle_message_received(message_event, client) when 'MESSAGE.STATUS' puts "Message status: #{message_event.message.status}" when 'USER.TYPING' puts "User typing" end status 200 { success: true, received: true }.to_json end # Handler for received messages def handle_message_received(event, client) return unless event.direction == 'INBOUND' case event.message.type when 'RCS_TEXT' puts "Received text: #{event.message.text}" client.messages.rcs.send_( text: "You said: #{event.message.text}", from: ENV["AGENT_ID"], to: event.conversation.from, quick_replies: [] ) when 'RCS_BUTTON_DATA' if event.message.button.payload == 'HELLO' client.messages.rcs.send_( text: "Hello! Button clicked successfully.", from: ENV["AGENT_ID"], to: event.conversation.from, quick_replies: [] ) end when 'RCS_MEDIA' puts "Received media message" when 'RCS_LOCATION_DATA' puts "Received location: #{event.message.coordinates}" end end get '/send-rcs/:phone_number' do content_type :json phone_number = params[:phone_number] begin card_message_res = client.messages.rcs.send_( cards: [ { media: "https://pncl.to/qT8_BlJF4YXj4yWZiKBjdonIh4iXet", title: "Hello, world!", subtitle: "This is an example card", buttons: [ { type: "openUrl", payload: "https://docs.pinnacle.sh/quickstart/rcs", title: "RCS Quickstart Guide" }, { type: "openUrl", payload: "https://docs.pinnacle.sh/api-reference/messages/send-rcs", title: "RCS API Reference" }, ] } ], quick_replies: [ { type: "trigger", metadata: "test_b", payload: "HELLO", title: "Hello!" } ], options: { validate: true }, from: ENV["AGENT_ID"], to: phone_number ) puts "\n=== RCS Card Message Sent ===" puts "To: #{phone_number}" puts "Response: #{card_message_res}" puts "============================\n" status 200 { success: true, response: card_message_res }.to_json rescue => e puts "Error sending RCS message: #{e.message}" status 500 { success: false, error: e.message }.to_json end end ``` ## Prerequisites Before proceeding, ensure you have obtained an RCS sandbox agent and API key as described in the [prerequisites](/quickstart/rcs). ## Installation Create a `Gemfile` in your project directory: ```ruby source "https://rubygems.org" gem "sinatra" gem "json" gem "dotenv" gem "rcs", "2.0.15" ``` Install the dependencies: ```bash bundle install ``` > **Info** > > This guide uses version `rcs 2.0.15`. Requires Ruby version >= 3.3.0 ## Configuration Create an `.env` file in your project root and add your Pinnacle API key: ``` PINNACLE_API_KEY="your_api_key" # pnclk_ AGENT_ID="your_agent_id" # agent_ PINNACLE_SIGNING_SECRET="your_signing_secret" # pss_ ``` ## Setting Up a Webhook To receive inbound RCS messages, you need to configure a webhook in the Pinnacle dashboard: 1. Navigate to **Development > Webhooks** in the Pinnacle dashboard 2. Click **Create new webhook** 3. Give your webhook a descriptive name 4. Enter your webhook endpoint URL * For local development, use an ngrok tunnel pointing to `localhost:4567/inbound-rcs` * For production, use your deployed server URL 5. Add your RCS sandbox agent to your webhook for it to receive messages. You must also whitelist the devices you want to test with by navigating to your sandbox agent and adding test device phone numbers. > **Info** > > Optionally, you can configure custom HTTP headers (e.g. `X-API-KEY`) to be > sent on every webhook delivery. Add them in the dashboard or via the > `headers` field on [`POST /webhooks/attach`](/api-reference/webhooks/attach-webhook). > The `PINNACLE-SIGNING-SECRET` header is reserved. ## Creating Your Webhook Endpoint Create a new Ruby file (e.g., `main.rb`) and add the following snippet to the right. The code above creates a Sinatra endpoint that: * Receives webhook POST requests at `/inbound-rcs` * Verifies the webhook signature using your signing secret * Handles inbound text messages, button clicks, media, and location shares * Echoes text messages back and responds to button presses ## Running Your Server Start the Sinatra server: ```bash ruby main.rb ``` Your server will start on `http://localhost:4567`. If you're using ngrok for local development, start it in a separate terminal: ```bash ngrok http 4567 ``` Use the ngrok URL (e.g., `https://abc123.ngrok.io/inbound-rcs`) as your webhook endpoint in the Pinnacle dashboard. ## Testing Your Webhook Go to `localhost:4567/send-rcs/+12345678910` (e.g., to your whitelisted number). If there are no errors, you should see something like ```json { "success": true, "response": { "messageId": "msg_7401", "segments": 1, "totalCost": 0.03, "recipient": "+18708977103", "sender": "agent_pinnacleNbjn", "status": "queued" } } ``` and receive a message on your whitelisted device like this: ![rcs message](https://server.trypinnacle.app/storage/v1/object/sign/vault/3/5f7289f9-8a7e-4c5c-9d51-776712ef6f8c/IMG_7658.jpg?token=eyJraWQiOiJzdG9yYWdlLXVybC1zaWduaW5nLWtleV9hOGI0YTI0NC00NzY4LTRhOTktYWI4MS1iNmZhNTZhNGQyZWYiLCJhbGciOiJIUzI1NiJ9.eyJ1cmwiOiJ2YXVsdC8zLzVmNzI4OWY5LThhN2UtNGM1Yy05ZDUxLTc3NjcxMmVmNmY4Yy9JTUdfNzY1OC5qcGciLCJpYXQiOjE3NjA5ODU5OTcsImV4cCI6MzE3MTIwOTg1OTk3fQ.5s3C9oHuU-jJKWRlihedlD_xALKLmxxn4ktXiwRvMkU) > **Warning** > > If you're not receiving any messages, make sure you have > > * Your RCS sandbox agent associated with your webhook > * Your test device is whitelisted If you tap "Hello!", your whitelisted device should receive a text saying "Hello! Button clicked successfully." If you reply with a text message, the server will echo it back. With that, your webhook should now be successfully receiving inbound RCS messages as well as message status updates for outbound messages! You can monitor delivery statuses by filtering events by direction: ```ruby if message_event.type == 'MESSAGE.STATUS' && message_event.direction == 'OUTBOUND' puts "Outbound message status: #{message_event.message.status}" end ``` For more detail about processing the message payload received, please view the [process method](/methods/process). > One API for RCS, iMessage, MMS, and SMS. Build, test, and scale every channel — send your first message in minutes, not weeks.