Rails

The FoPost Ruby client wired into Rails, with credentials, ActiveJob jobs, and a webhook engine.

fopost-rails is a thin wrapper over the Ruby SDK. Every request, retry, model, and error class lives there; what this gem adds is Rails wiring: config.fopost and Rails credentials, a memoized thread-safe client, an install generator, ActiveJob jobs so publishing never blocks a request, and a mountable engine that verifies incoming webhooks.

Needs Ruby 3.1 or newer and Rails 7.0 or newer.

bundle add fopost-rails

This is a 0.x release. The surface is still settling and a minor version may break something. Pin an exact version if that matters to you.

Published on RubyGems. Source and issues: github.com/fopost/fopost-rails. MIT licensed.

Configuration

Write the initializer:

bin/rails generate fopost:install

Create an API key at Settings → API Keys in the dashboard and put it somewhere the app can read it:

bin/rails credentials:edit
fopost:
  api_key: fp_your_key_here
  default_workspace_id: ws_...
  webhook_secret: whsec_...

Or in the environment:

FOPOST_API_KEY=fp_your_key_here

Every setting resolves the same way: what you set explicitly wins, then Rails credentials under fopost:, then the environment, then the default.

SettingCredentials keyEnvironmentDefault
api_keyfopost: api_key:FOPOST_API_KEYnone, required
base_urlbase_urlFOPOST_BASE_URLthe hosted API
timeouttimeoutFOPOST_TIMEOUT30.0
max_retriesmax_retriesFOPOST_MAX_RETRIES3
default_workspace_iddefault_workspace_idFOPOST_WORKSPACE_IDnone
webhook_secretwebhook_secretFOPOST_WEBHOOK_SECRETnone
queue_namequeue_nameFOPOST_QUEUEdefault

Set them in the initializer:

Fopost::Rails.configure do |config|
  config.api_key = Rails.application.credentials.dig(:fopost, :api_key)
  config.queue_name = 'social'
end

or from config/application.rb:

config.fopost.default_workspace_id = 'ws_...'

Quick start

Fopost::Rails.client is a configured Fopost::Client, memoized and safe to call from any thread. The Ruby SDK page documents the full resource surface.

class PostsController < ApplicationController
  def index
    @posts = Fopost::Rails.client.posts.list(status: 'scheduled')
  end

  def create
    post = Fopost::Rails.client.posts.create(
      workspace_id: Fopost::Rails.config.default_workspace_id,
      content: params.require(:text),
      accounts: params.require(:account_ids)
    )

    Fopost::Rails::PublishJob.perform_later(post.id)
    redirect_to posts_path, notice: 'Queued for publishing.'
  end
end

Errors are the SDK's, so one rescue_from covers the lot:

rescue_from Fopost::PaymentRequiredError do |error|
  redirect_to error.upgrade_url, alert: error.message
end

rescue_from Fopost::Error do |error|
  Rails.logger.error("FoPost: #{error}")
  head :bad_gateway
end

Background jobs

Publishing reaches a third-party network, so it belongs off the request cycle.

# Publish something that already exists.
Fopost::Rails::PublishJob.perform_later(post.id)

# Compose and, optionally, send in one job.
Fopost::Rails::CreatePostJob.perform_later(
  content: 'Shipping today.',
  accounts: account_ids,
  publish: true
)

# Or schedule it, and pass anything else the SDK takes through `options`.
Fopost::Rails::CreatePostJob.perform_later(
  workspace_id: 'ws_...',
  content: ['First post in the thread', 'And the reply'],
  accounts: account_ids,
  status: 'scheduled',
  schedule_at: 1.hour.from_now,
  options: { labels: ['launch'], title: 'Launch week' }
)

workspace_id falls back to config.default_workspace_id. Both jobs run on config.queue_name.

When the API answers 429, the job is re-enqueued for exactly the interval the API asked for in Retry-After, capped at a minute, up to five attempts. Every other Fopost::Error is left to your queue's own error handling.

Publishing returns once delivery is queued, not once it is live. Subscribe to fopost.post.published for that.

Receiving webhooks

Mount the engine:

# config/routes.rb
mount Fopost::Rails::Engine => '/fopost'

That serves POST /fopost/webhooks. Create a webhook pointing at it, copy the secret it shows you once into config.webhook_secret, and subscribe:

# config/initializers/fopost_webhooks.rb
ActiveSupport::Notifications.subscribe('fopost.post.published') do |*, payload|
  payload[:event]        # "post.published"
  payload[:data]         # the event body FoPost sent
  payload[:timestamp]    # ISO 8601, when FoPost sent it
  payload[:delivery_id]  # X-FoPost-Delivery, unique per attempt
  payload[:payload]      # the whole parsed body
end

Two notifications fire per verified delivery: fopost.<event>, and fopost.webhook as a catch-all. The events are post.published, post.failed, post.partially_failed, delivery.published, delivery.failed, delivery.delayed, and account.health_changed.

Verification is not optional and not yours to write. FoPost signs the exact bytes of the request body with HMAC-SHA256, keyed by the webhook secret, and sends the hex digest as X-FoPost-Signature: sha256=<digest>. The controller recomputes it over the raw body and compares in constant time; a mismatch is a 401 and publishes nothing, and an unconfigured secret is a 503 rather than a pretended success.

To sign a request yourself, in a request spec:

body = { event: 'post.published', data: { postId: 'post_1' } }.to_json

post '/fopost/webhooks',
     params: body,
     headers: {
       'CONTENT_TYPE' => 'application/json',
       'X-FoPost-Signature' => Fopost::Rails::WebhookSignature.sign(body, secret)
     }

Testing your app

Swap the client for one wired to your own transport and nothing touches the network:

Fopost::Rails.client = Fopost::Client.new(api_key: 'fp_test', transport: my_stub)

Fopost::Rails.reset! puts config and client back to their defaults between tests.

Next

Related documentation
  • SDKs & Integrations

    Official FoPost clients for TypeScript, Python, PHP, Ruby, Go, Rust, Java, .NET, Swift, Kotlin, Dart, and Elixir, framework integrations from Laravel to Next.js, and tooling for the CLI, CI, Terraform, and the automation platforms.

  • SDKs Overview

    Every official FoPost client, what it covers, and how to pick one.

  • TypeScript SDK

    The official TypeScript and Node.js client for the FoPost API.

  • Python SDK

    The official Python client for the FoPost API.

  • PHP SDK

    The official PHP client for the FoPost API, with no framework and no HTTP library.

  • Ruby SDK

    The official Ruby client for the FoPost API, with no runtime dependencies.

  • Go SDK

    The official Go client for the FoPost API.

  • Rust SDK

    The official Rust client for the FoPost API, async and built on reqwest.

Was this helpful?

On this page