Cookie consent for Ruby on Rails

The banner is one tag in application.html.erb, before your importmap or bundle. It stays across Turbo Drive navigations, and the consents your forms ask for are recorded from the controller with one HTTP call, each with a signed receipt.

Set it up with your AI agent

Connect the Croombit MCP server to your editor or coding agent and paste this. It creates the site, the cookie categories and the banner, and edits your code.

Using Croombit: create a site for this project and keep the cookie categories it comes with. Write the banner text and publish the banner. Create a TEST publishable key for localhost:3000. This is a Rails app: add the cmp.js tag with javascript_include_tag first in <head> of app/views/layouts/application.html.erb, with the key from ENV["CROOMBIT_PUBLISHABLE_KEY"], allow it in our CSP if we have one, and record the newsletter checkbox of our signup controller with a POST to /v1/consent using CROOMBIT_SECRET_KEY.

The agent works in TEST. Going live takes two steps in the console: verify your domain and swap in a LIVE key — see going live.

Or by hand

1. Add the tag to the layout

data: { tc_api_key: … } renders as data-tc-api-key. Use pk_test_… in development and pk_live_… in production.

<%# app/views/layouts/application.html.erb %>
<head>
  <meta charset="utf-8">
  <%= javascript_include_tag "https://croombit.com/cmp.js",
        data: { tc_api_key: ENV.fetch("CROOMBIT_PUBLISHABLE_KEY") } %>
  <%= csrf_meta_tags %>
  <%# …everything else after it %>
  <%= javascript_importmap_tags %>
</head>

2. Allow it in your CSP

# config/initializers/content_security_policy.rb — only if you enabled a CSP
Rails.application.configure do
  config.content_security_policy do |policy|
    policy.script_src  :self, "https://croombit.com"
    policy.connect_src :self, "https://api.croombit.com"
  end
end

3. Hold back trackers

Tags in your views: type="text/plain" plus data-tc-consent. On a Turbo visit, tagged scripts on the new page run as soon as their category is granted.

<script type="text/plain" data-tc-consent="analytics" async
        src="https://www.googletagmanager.com/gtag/js?id=G-XXXX"></script>
<script type="text/plain" data-tc-consent="analytics">
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', 'G-XXXX');
</script>

4. Record consents from your controller

A plain POST /v1/consent with your secret key. The Idempotency-Key makes a retry safe. See Record and query consent.

require "net/http"

# e.g. in the controller that handles your signup form
uri = URI("https://api.croombit.com/v1/consent")
req = Net::HTTP::Post.new(uri,
  "Authorization" => "Bearer #{ENV.fetch('CROOMBIT_SECRET_KEY')}",
  "Content-Type" => "application/json",
  "Idempotency-Key" => SecureRandom.uuid)
req.body = {
  subject: "email:#{user.email}",
  purpose: "newsletter",
  state: params[:newsletter] == "1" ? "GRANTED" : "DENIED",
  channel: "signup_form"
}.to_json
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
raise "Croombit: #{res.code} #{res.body}" unless res.is_a?(Net::HTTPSuccess)

Questions

Does it work with Turbo Drive?

Yes. Turbo swaps the page body on each visit; the banner and the floating button are carried across, and the decision applies to every page.

Does it work with Rails 6 and Webpacker?

Yes. Keep the tag first in <head>, before javascript_pack_tag.