For a small payment-webhook receiver, when would you choose Sinatra over Rails, and what would you then have to add yourself?
answer
- a routing DSL on Rack, little else
- no ORM, migrations or job queue
- idempotency is yours to build
- sinatra-contrib for Namespace and JSON
- move when you are rebuilding Rails
basics
~20 sChoose Sinatra when the service is a handful of endpoints with no HTML UI or shared models: it gives routing, filters, helpers and templates on Rack. Persistence, background jobs, code layout and duplicate-event handling are yours to add.
solid answer
~50 sA webhook receiver is two or three routes: verify the signature, record the event ID so provider retries are ignored, enqueue the work and answer 2xx quickly. Sinatra covers that in one file with routes, a `before` filter, a helper and `halt`, with rack-protection on by default and a short dependency list (Rack, Mustermann, Tilt, rack-protection, rack-session, logger). What it leaves out you add yourself: a database library and migrations, a job queue, a code layout and loading scheme, request validation and environment configuration beyond `configure`. Rails ships an ORM with migrations, a job abstraction, mailers, generators and conventions, which pay off once the service grows models, an admin UI or a larger team. If the receiver must share models with an existing Rails app, a controller in that app is usually simpler than a second service. Decide on scope and ownership, not on speed claims.
code
ruby · 9 lines# Gemfile for a Sinatra webhook receiver
source 'https://rubygems.org'
gem 'sinatra', '~> 4.2'
gem 'rackup' # needed by Sinatra 4's run!
gem 'puma'
gem 'sinatra-contrib' # Namespace, JSON, ConfigFile
# plus a database gem for the processed-event table
# and a job-queue gem for the work itselfgo deeper
Recall that Sinatra is a small routing DSL on Rack, while Rails is a full-stack framework with an ORM, jobs and generators.
Explain what a Sinatra service must add itself, such as persistence, jobs and code layout, and which sinatra-contrib extensions help as it grows.
Size the choice to the job: list the receiver's duties, map each to Sinatra or a library, and name the signals that the service has outgrown it.
Frame the organisational trade-off: one more small service to own and deploy versus a controller inside an existing Rails monolith.
## What Sinatra actually is **Sinatra** is a small domain-specific language for web applications on top of **Rack**, Ruby's common interface between web servers and frameworks. The sinatra 4.2 gem provides: - routing: `get`, `post` and the other verbs, with patterns, conditions and `params`; - `before`/`after` **filters**, **helpers**, and `halt`, `pass` and `redirect` for control flow; - rendering through Tilt (`erb`, `haml` and others), static files from `public_folder`; - settings with `set` and `configure`, cookie sessions when you `enable :sessions`, and rack-protection middleware on by default; - a built-in launcher, `run!`, and a development error page. Its runtime dependencies are Rack, Mustermann, Tilt, rack-protection, rack-session and logger. Everything else is a choice you make. ## What a webhook receiver needs A payment-provider webhook endpoint has a narrow job: 1. **Verify** the signature over the raw body before trusting anything. 2. **Deduplicate**: providers retry on timeouts and errors, so store each event ID under a unique key and treat a repeat as success. 3. **Enqueue** the real work instead of doing it inside the request. 4. **Answer quickly** with a 2xx so the provider does not retry. 5. **Observe**: log every outcome, including rejected signatures. Steps 1, 4 and 5 are a filter, a helper, a route and an after filter in Sinatra. Steps 2 and 3 need libraries Sinatra does not include: a database gem for the event table and a queue gem for the jobs. ## Side by side | Concern | Sinatra 4.2 | Rails | |---|---|---| | Routing and request handling | yes, a DSL in one file | yes, routes plus controllers | | Database access and migrations | add a library yourself | bundled ORM with migrations | | Background jobs | add a queue library | bundled job abstraction | | Code layout and loading | your own `require`s | conventions and autoloading | | Generators, mailers, admin UI | none | bundled or conventional | | Boot footprint | a few gems | the whole framework | Both are Rack applications, so the same servers and Rack middleware work with either, and a Rails app can mount a Sinatra app when the two must live together. ## A reasonable first version A first Sinatra receiver can stay in one modular class: 1. a `before '/webhooks/*'` filter reads the raw body and verifies the signature, halting with 401 on a mismatch; 2. the route parses the JSON, inserts the event ID into a table with a unique index, and treats a duplicate as already handled; 3. it enqueues a job carrying the event ID and returns `204`; 4. an `after` filter logs the path and final status of every delivery. Everything else, retries of the job itself, alerting, reporting, lives in the queue and the worker, not in Sinatra. ## Growing a Sinatra service When a receiver grows, the usual steps are: - split routes into several `Sinatra::Base` subclasses, each a Rack app of its own; - use **sinatra-contrib**: `Sinatra::Namespace` groups routes under a prefix, `Sinatra::JSON` adds a `json` helper, `Sinatra::ConfigFile` reads settings per environment from YAML; - keep business logic in plain Ruby objects that the routes call, so the framework stays a thin edge. The signal to move is when you find yourself rebuilding what a full-stack framework already has: model validations, migrations across several tables, mailers, an admin area, and a team that expects conventions to tell it where code goes. ## Weak arguments to avoid - **"Sinatra is faster."** Framework overhead rarely dominates a webhook's time; database and network calls do. Measure before citing it. - **"Sinatra is only for prototypes."** It serves production traffic; the question is how much you want to assemble yourself. - **"Sinatra has no security."** rack-protection is on by default and `host_authorization` exists since 4.1; the security that matters here, signature checks and idempotency, is application code in either framework. - **"A second service is always cleaner."** If the handler must update models an existing Rails app owns, a separate service adds a deploy, a database connection and a contract to keep in sync. Interviewers use this question to see whether you size a tool to the job and can name what you would own afterwards, not whether you prefer one framework.
- How do you make the receiver safe against the provider delivering the same event twice?Store each event ID in a table with a unique index before enqueuing work, inside the same transaction as any state change. On a duplicate-key error, answer 2xx without redoing the work. Sinatra has nothing for this; it is application code in any framework.
- When would you add the webhook to an existing Rails app instead of a new Sinatra service?When the handler updates models that app owns, shares its database and deploy pipeline, or the team already runs it. A second service then adds a deployment, a connection pool and a contract to keep in sync, for little gain.
saying these in an interview costs you the question
- Sinatra is only for prototypes and cannot serve production traffic.
- Sinatra ships a model layer and migrations like Rails.
- Sinatra apps are not Rack apps, so Rack middleware cannot wrap them.
- Pick Sinatra because it is always faster in production.
- Sinatra has no session support.