Loading...
Loading...
The SignBolt Ruby gem gives you a clean, idiomatic Ruby client for the SignBolt REST API. Works in Rails, Sinatra, Hanami, or any Ruby application.
Idiomatic Ruby API with snake_case throughout
Works seamlessly in Rails 7+ and Ruby 3.1+
Hash-based arguments with optional keyword args
Webhook signature verification helper
Faraday-based HTTP with automatic retries
Ruby 3.1 or newer required. The gem uses Faraday for HTTP and is compatible with Faraday 2.x. For Rails integration, generate an initialiser in `config/initializers/signbolt.rb`.
gem install signbolt# Or add to Gemfile: gem 'signbolt', '~> 1.0'Generate an API key from your SignBolt dashboard. Store in Rails credentials (`rails credentials:edit`) or environment variables. Never commit API keys to source control.
# Rails config/initializers/signbolt.rb
SignBolt.configure do |config|
config.api_key = Rails.application.credentials.signbolt[:api_key]
config.webhook_secret = Rails.application.credentials.signbolt[:webhook_secret]
config.timeout = 30
config.max_retries = 3
end
# Or in plain Ruby
require 'signbolt'
client = SignBolt::Client.new(
api_key: ENV.fetch('SIGNBOLT_API_KEY'),
base_url: 'https://signbolt.au/api/v1', # default
timeout: 30,
max_retries: 3
)
# In a Rails service
class SignBoltService
def self.client
@client ||= SignBolt::Client.new(api_key: Rails.application.credentials.signbolt[:api_key])
end
endSend a document to a recipient for signature.
require 'signbolt'
client = SignBolt::Client.new(api_key: ENV.fetch('SIGNBOLT_API_KEY'))
def send_contract(client)
# Read PDF from disk
pdf_bytes = File.binread('contracts/service-agreement.pdf')
response = client.documents.send(
file: pdf_bytes,
filename: 'service-agreement.pdf',
recipients: [
{
email: 'client@example.com',
name: 'Jane Client',
role: 'signer'
}
],
subject: 'Please sign: Service Agreement',
message: 'Hi Jane, please review and sign the attached service agreement.',
fields: [
{
type: 'signature',
page: 1,
x: 100,
y: 500,
width: 200,
height: 60,
required: true,
recipient_email: 'client@example.com'
},
{
type: 'date',
page: 1,
x: 350,
y: 510,
width: 120,
height: 40,
required: true,
recipient_email: 'client@example.com'
}
],
webhook_url: 'https://api.yoursite.com/webhooks/signbolt',
expires_in_days: 14
)
puts "Document ID: #{response.document_id}"
puts "Signing URL: #{response.signing_urls.first.url}"
puts "Status: #{response.status}"
response
end
send_contract(client)
# Rails controller
class ContractsController < ApplicationController
def send_for_signature
pdf = params[:contract]
response = SignBoltService.client.documents.send(
file: pdf.read,
filename: pdf.original_filename,
recipients: [{ email: params[:email], name: params[:name] }],
fields: [
{
type: 'signature',
page: 1,
x: 100,
y: 500,
width: 200,
height: 60,
required: true
}
]
)
render json: {
document_id: response.document_id,
signing_url: response.signing_urls.first.url
}
rescue SignBolt::Error => e
render json: { error: e.message }, status: :unprocessable_entity
end
endVerify webhook signatures in Rails or Sinatra.
# Rails
# config/routes.rb
Rails.application.routes.draw do
post '/webhooks/signbolt', to: 'webhooks#signbolt'
end
# app/controllers/webhooks_controller.rb
class WebhooksController < ApplicationController
skip_before_action :verify_authenticity_token, only: [:signbolt]
def signbolt
signature = request.headers['X-SignBolt-Signature']
body = request.raw_post
unless SignBoltService.client.webhooks.verify(body: body, signature: signature, secret: Rails.application.credentials.signbolt[:webhook_secret])
head :unauthorized and return
end
event = JSON.parse(body)
case event['type']
when 'document.signed'
document_id = event['data']['documentId']
signer = event['data']['signerEmail']
Rails.logger.info "Document #{document_id} signed by #{signer}"
# Update database, enqueue notification job
ProcessSignedDocumentJob.perform_later(document_id)
when 'document.completed'
document_id = event['data']['documentId']
Rails.logger.info "Document #{document_id} fully signed"
# Download final PDF
DownloadSignedPdfJob.perform_later(document_id)
when 'document.declined'
Rails.logger.info "Signer declined: #{event['data']['signerEmail']}"
when 'document.expired'
Rails.logger.info "Document #{event['data']['documentId']} expired"
end
head :ok
end
end
# Sinatra
require 'sinatra'
require 'signbolt'
require 'json'
client = SignBolt::Client.new(api_key: ENV.fetch('SIGNBOLT_API_KEY'))
webhook_secret = ENV.fetch('SIGNBOLT_WEBHOOK_SECRET')
post '/webhooks/signbolt' do
signature = request.env['HTTP_X_SIGNBOLT_SIGNATURE']
body = request.body.read
halt 401, { error: 'Invalid signature' }.to_json unless client.webhooks.verify(body: body, signature: signature, secret: webhook_secret)
event = JSON.parse(body)
if event['type'] == 'document.completed'
puts "Document #{event['data']['documentId']} completed"
end
{ received: true }.to_json
endHandle errors with SignBolt::Error and its subclasses.
require 'signbolt'
client = SignBolt::Client.new(api_key: ENV.fetch('SIGNBOLT_API_KEY'))
def send_with_error_handling(client, pdf_bytes)
response = client.documents.send(
file: pdf_bytes,
filename: 'contract.pdf',
recipients: [{ email: 'client@example.com', name: 'Client' }],
fields: [{ type: 'signature', page: 1, x: 100, y: 500, width: 200, height: 60 }]
)
response
rescue SignBolt::BadRequestError => e
Rails.logger.error "Bad request: #{e.message}"
# e.details has field-level validation errors
raise
rescue SignBolt::AuthenticationError => e
Rails.logger.error 'Invalid API key β check credentials'
raise
rescue SignBolt::ForbiddenError => e
Rails.logger.error 'API access requires Business plan β upgrade at /pricing'
raise
rescue SignBolt::PayloadTooLargeError => e
Rails.logger.error 'PDF exceeds 25MB β compress or split'
raise
rescue SignBolt::RateLimitError => e
Rails.logger.warn 'Rate limited β SDK will retry automatically'
raise
rescue SignBolt::ServerError => e
Rails.logger.error 'SignBolt server error β transient, safe to retry'
raise
rescue SignBolt::Error => e
# Catch-all for any SignBolt error not caught above
Rails.logger.error "SignBolt error: #{e.message}"
raise
rescue StandardError => e
# Non-SignBolt error (network, parsing)
Rails.logger.error "Unexpected error: #{e.message}"
raise
endList signed documents with pagination.
require 'signbolt'
client = SignBolt::Client.new(api_key: ENV.fetch('SIGNBOLT_API_KEY'))
# Recent completed documents
def get_recent_signed(client)
page1 = client.documents.list(status: 'completed', limit: 50)
puts "Got #{page1.data.size} documents"
all_docs = page1.data
while page1.next_cursor
page1 = client.documents.list(status: 'completed', limit: 50, cursor: page1.next_cursor)
all_docs.concat(page1.data)
end
all_docs
end
# Filter by date range
docs = client.documents.list(
created_after: Time.utc(2026, 3, 1).iso8601,
created_before: Time.utc(2026, 4, 1).iso8601,
limit: 100
)
docs.data.each do |doc|
puts "#{doc.filename} β signed by #{doc.signer_email} on #{doc.signed_at}"
end
# Iterate all documents using Enumerable
client.documents.list_all(status: 'completed').each do |doc|
puts doc.filename
endThe SignBolt API enforces rate limits to ensure fair use across all customers. The SDK respects these limits with automatic retries.
Standard rate limit: 60 requests per minute per API key.
Burst allowance: up to 120 requests in a 60-second window.
Hourly limit: 3,000 requests per API key per hour.
Send endpoint: limited to 10 documents per minute.
Gem retries rate-limited requests automatically with exponential backoff.
Yes. Install the gem, create `config/initializers/signbolt.rb`, configure with your API key from credentials. Use via the top-level `SignBolt::Client` class or a Rails service object. Compatible with Rails 7 and 8.
Yes, and it's the recommended pattern for webhook processing. Receive the webhook in a controller (do the signature verification synchronously), then enqueue a Sidekiq job to do the actual work. This keeps webhook responses fast and lets you retry reliably.
Yes. `SignBolt::Client` instances are thread-safe and can be shared across threads (useful for Puma and Sidekiq). Internally the gem uses Faraday with a thread-safe connection pool.
Catch `SignBolt::Error` (or specific subclasses like `RateLimitError`, `ServerError`) in controllers. Log errors to your observability stack. For background jobs, let Sidekiq's retry logic handle transient failures β the SDK's built-in retries handle short-timeframe issues.
Yes. The gem has no Rails-specific dependencies. Initialise the client in your app setup, inject via dependency container (Hanami) or as a global/instance variable (Sinatra).
Use a sandbox API key (prefix `sb_test_`). Requests don't send emails or bill against plan limits. For unit tests, stub the gem's HTTP calls using WebMock or VCR β the gem uses Faraday so standard Ruby HTTP testing tools work.
Not supported. The gem requires Ruby 3.1+ to use pattern matching, keyword argument strictness, and other 3.x features. Ruby 2.7 and 3.0 reached end-of-life β upgrade before adopting.
API access is included in the Business plan ($24/month). Upgrade to get your API key.