Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Fix the driver behind crashes, sound loss and screen glitches3Clear out junk files and repair common Windows errorsSome links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
To receive a webhook in Ruby, expose an HTTPS POST endpoint, read the exact raw request body and headers, verify the sender’s signature before parsing JSON, record the delivery ID, enqueue the work, and return a 2XX response quickly. The pattern works in Sinatra, Rails, and other Rack applications; only the provider-specific header names and signature algorithm change.
How a Ruby webhook endpoint works
A webhook provider sends an HTTP POST request to a URL you configure. The request normally contains JSON plus headers identifying the event, delivery, and authentication signature. Your endpoint should perform these operations in order:
- Accept only the HTTP method and route you configured.
- Read the request body as an unchanged byte/string sequence.
- Read the provider’s signature and event headers.
- Verify the signature with the shared secret, using constant-time comparison.
- Parse JSON only after verification succeeds.
- Validate the event type, action, and required fields.
- Persist the delivery ID and enqueue processing before acknowledging.
- Return the provider’s required 2XX status.
Do not log secrets or entire payloads containing personal data. Keep the endpoint on HTTPS and subscribe only to event types your application handles.
Sinatra implementation
This complete example verifies GitHub-style signatures, records a delivery identifier in a placeholder method, and returns 202 Accepted. Set WEBHOOK_SECRET in the process environment rather than in source control.
#1 Best Overall
require "sinatra"
require "json"
require "openssl"
SECRET = ENV.fetch("WEBHOOK_SECRET")
helpers do
def constant_time_equal?(a, b)
Rack::Utils.secure_compare(a, b)
rescue ArgumentError
false
end
end
post "/webhook" do
request.body.rewind
raw_body = request.body.read
signature = request.env["HTTP_X_HUB_SIGNATURE_256"]
expected = "sha256=" + OpenSSL::HMAC.hexdigest(
OpenSSL::Digest.new("sha256"), SECRET, raw_body
)
halt 401 unless signature && constant_time_equal?(expected, signature)
event_name = request.env["HTTP_X_GITHUB_EVENT"]
delivery_id = request.env["HTTP_X_GITHUB_DELIVERY"]
begin
payload = JSON.parse(raw_body)
rescue JSON::ParserError
halt 400
end
halt 400 unless delivery_id && event_name
# Replace these with a database insert and background-job enqueue.
# persist_delivery_once(delivery_id, event_name, payload)
# WebhookJobs.perform_async(delivery_id)
status 202
end
GitHub’s documented signature is an HMAC-SHA256 hexadecimal digest over the raw body, prefixed with sha256=. The comparison must be constant-time; ordinary == is not an appropriate security decision. GitHub also advises storing the secret securely and never hardcoding or committing it.
Routing by event and action
After verification, dispatch narrowly. GitHub payloads commonly include an event header and an action field. Check both so an event you do not support cannot accidentally invoke a handler.
case event_name
when "issues"
action = payload.fetch("action")
case action
when "opened"
# enqueue issue-opened work
when "closed"
# enqueue issue-closed work
end
when "push"
# enqueue push processing
else
# Ignore subscribed-but-unhandled events safely
end
Use fetch or explicit validation for fields that are required by your business logic. Treat missing or malformed fields as a bad request or a failed job, according to your provider’s retry policy.
Rails implementation
Create a dedicated POST route and controller action. Obtain the raw body before Rails or middleware parses or transforms it.
# config/routes.rb
post "/webhooks/github", to: "webhooks#github"
# app/controllers/webhooks_controller.rb
class WebhooksController < ActionController::API
def github
raw_body = request.raw_post
signature = request.headers["X-Hub-Signature-256"]
secret = ENV.fetch("WEBHOOK_SECRET")
expected = "sha256=" + OpenSSL::HMAC.hexdigest(
OpenSSL::Digest.new("sha256"), secret, raw_body
)
head :unauthorized unless signature &&
Rack::Utils.secure_compare(expected, signature)
begin
payload = JSON.parse(raw_body)
rescue JSON::ParserError
return head :bad_request
end
event_name = request.headers["X-GitHub-Event"]
delivery_id = request.headers["X-GitHub-Delivery"]
return head :bad_request if event_name.blank? || delivery_id.blank?
# Insert delivery_id with a unique database constraint, then enqueue.
# GithubWebhookJob.perform_later(delivery_id, event_name, payload)
head :accepted
end
end
Use the raw-body facility before JSON parsing. Avoid middleware that replaces the body, changes whitespace, normalizes encoding, or parses and re-serializes JSON before verification. Return a 4XX response for unauthenticated or malformed requests, and a 2XX response only after accepted work has been durably recorded or queued.
Provider-specific signature verification
GitHub
Read X-Hub-Signature-256, calculate HMAC-SHA256 over the unchanged body with the webhook secret, and compare the resulting sha256=... value in constant time. Use X-GitHub-Event for the event name and X-GitHub-Delivery as the delivery identifier.
Rank #2
Stripe
Use the Stripe Ruby SDK’s webhook construction and signature-verification API rather than adapting GitHub code. Stripe’s header format, timestamp tolerance, and exception classes are provider-specific. Preserve the unmodified body until the SDK verifies it, and handle the SDK’s signature-verification errors explicitly.
The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Other providers
Do not assume every sender uses HMAC-SHA256, the same header names, or the same replay rules. Follow that provider’s current documentation for canonicalization, timestamp windows, key rotation, and required response status.
Fast acknowledgements, retries, and idempotency
GitHub’s handling guidance requires a 2XX response within 10 seconds of receiving a delivery. Slow API calls, image processing, or database-heavy work should run in a background queue, not inside the request. Resque is one Ruby queue option named in GitHub’s guidance; any durable job system is acceptable.
Persist before returning
Insert the delivery ID, event name, raw payload (or a validated representation), and received timestamp in a transaction. Enqueue a job using an outbox or equivalent mechanism so a process crash cannot acknowledge an event that was never saved.
Make retries harmless
Providers retry timeouts and non-success responses. Put a unique constraint on the delivery ID and make each handler idempotent: a repeated delivery should produce the same final state, not duplicate an email, charge, or database record. The GitHub delivery ID is useful for detecting replays and investigating failures.
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteReturn status deliberately
- 2XX: authentication passed and the delivery was durably accepted.
- 4XX: bad signature, malformed body, unsupported authentication, or invalid required fields.
- 5XX: a transient server failure when you want the provider to retry.
Configuration and security checklist
- Use an HTTPS webhook URL and verify the route is reachable from the provider.
- Keep secrets in environment variables or a secret manager; rotate them using the provider’s supported procedure.
- Read the raw body exactly once and verify before parsing.
- Use constant-time comparison for MACs and signatures.
- Limit request size and reject unexpected content types where practical.
- Subscribe only to required event types.
- Validate event, action, identifiers, and required fields after authentication.
- Log delivery ID, event type, verification result, processing status, and latency, but never secrets.
- Protect replay-sensitive handlers with delivery-ID uniqueness and, where the provider supports it, timestamp validation.
Testing a Ruby webhook endpoint
Test the security boundary and the operational path separately:
Rank #3
- Send a valid body and correctly computed signature; expect a 2XX response and one durable delivery record.
- Change one byte of the body without changing the signature; expect 401.
- Use a malformed signature or omit it; expect 401.
- Send invalid JSON with a valid MAC; expect 400.
- Send the same delivery ID twice; verify the second request creates no duplicate side effect.
- Exercise unknown event names and unsupported actions; verify they are ignored or rejected according to your contract.
- Simulate a queue or database outage and confirm the endpoint returns a retryable failure rather than falsely acknowledging.
Use the provider’s delivery history and redelivery controls when diagnosing production failures. Record correlation IDs so an incoming request can be followed through the queue and handler.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Troubleshooting common failures
Every request returns 401
Check that the application reads the provider’s actual header name, the secret belongs to this endpoint, and the body has not been parsed or modified first. Log the header’s presence and calculated length, never the secret itself. Confirm that the expected value includes the provider-required prefix such as sha256=.
Signature works locally but fails in production
Verify the production secret, proxy configuration, request encoding, and middleware order. A reverse proxy or body parser that changes bytes will invalidate an otherwise correct HMAC.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →The provider reports timeouts
Measure time spent before the response. Move third-party calls and expensive work to a queue, persist the delivery first, and return a 2XX within the provider’s deadline. For GitHub, that deadline is 10 seconds.
Events are processed twice
Retries are normal. Add a unique database constraint for the delivery ID, check it before enqueueing, and make the job’s external side effects idempotent.
JSON parsing raises an exception
Parse only after successful authentication, catch the parser exception, enforce a request-size limit, and return 400 for invalid JSON. Do not attempt to “repair” a signed body before verification.
Or skip the browser setup
If you also need clean screenshots of webhook dashboards, documentation, or delivery history, ScreenshotNeo provides a single HTTP call rather than a browser automation stack. It accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing result. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.
Free tools Windows power users keep installed
One-click scans. No signup required.
For a screenshot, use the documented API parameters and save the binary response:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
See the ScreenshotNeo API documentation for options such as full-page capture, CSS selectors, custom headers and cookies, JavaScript, waits, blocking, PDF output, signed links, asynchronous jobs, and bulk capture. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
Frequently Asked Questions
Should a webhook endpoint return 200 or 202?
Use the status your provider accepts after the delivery is durably recorded or queued. A 202 clearly indicates asynchronous acceptance; follow the sender’s documented response requirements.
Can I verify a webhook after calling JSON.parse?
No. Verify the unchanged raw body first. Parsing and re-serializing can alter whitespace, encoding, or key representation and invalidate the signature.
How do I handle webhook secret rotation?
Use the provider’s documented rotation process. During an overlap window, support the currently valid keys in a controlled order, then remove the old key after deliveries using it have ceased.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

