From 4f08403c62f52458ee2606bbfc56e1c008bd5dd6 Mon Sep 17 00:00:00 2001 From: Bernardo Anderson Date: Wed, 4 Feb 2026 22:34:28 -0600 Subject: [PATCH 1/7] CP-11565 - Update webhook secret to load for new accounts --- app/models/account.rb | 14 ++++++++++++++ bin/start_production | 7 +++++++ bin/start_staging | 7 +++++++ 3 files changed, 28 insertions(+) diff --git a/app/models/account.rb b/app/models/account.rb index d7d3c96d5..d397efd2e 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -57,6 +57,20 @@ class Account < ApplicationRecord validates :external_account_id, uniqueness: true, allow_nil: true + after_create :create_careerplug_webhook + + private + + def create_careerplug_webhook + return unless ENV['CAREERPLUG_WEBHOOK_SECRET'].present? + + webhook_urls.create!( + url: 'https://www.careerplug.com/api/docuseal/events', + events: %w[form.viewed form.started form.completed form.declined], + secret: { 'X-CareerPlug-Secret' => ENV.fetch('CAREERPLUG_WEBHOOK_SECRET') } + ) + end + scope :active, -> { where(archived_at: nil) } def self.find_or_create_by_external_id(external_id, name, attributes = {}) diff --git a/bin/start_production b/bin/start_production index bad489746..626ede920 100755 --- a/bin/start_production +++ b/bin/start_production @@ -170,6 +170,7 @@ fetch_env_variables() { export SECURED_STORAGE_BUCKET=$(echo "$SECRET_JSON" | jq -r '.secured_storage_bucket') export SECURED_STORAGE_REGION=$(echo "$SECRET_JSON" | jq -r '.secured_storage_region') export ENCRYPTION_SECRET=$(echo "$SECRET_JSON" | jq -r '.ENCRYPTION_SECRET // empty') + export CAREERPLUG_WEBHOOK_SECRET=$(echo "$SECRET_JSON" | jq -r '.CAREERPLUG_WEBHOOK_SECRET // empty') # Validate that we got the values if [ "$DB_HOST" = "null" ] || [ "$REDIS_URL" = "null" ] || [ "$S3_ATTACHMENTS_BUCKET" = "null" ] || [ -z "$DB_HOST" ] || [ -z "$REDIS_URL" ] || [ -z "$S3_ATTACHMENTS_BUCKET" ]; then @@ -234,6 +235,12 @@ fetch_env_variables() { echo "✓ ENCRYPTION_SECRET written to .env.production" fi + # Add CareerPlug webhook secret if it exists + if [ -n "$CAREERPLUG_WEBHOOK_SECRET" ]; then + echo "CAREERPLUG_WEBHOOK_SECRET=$CAREERPLUG_WEBHOOK_SECRET" >> ./.env.production + echo "✓ CAREERPLUG_WEBHOOK_SECRET written to .env.production" + fi + echo "✓ Environment variables successfully retrieved and written to .env.production" } diff --git a/bin/start_staging b/bin/start_staging index 1c8512b4f..176c2a26d 100755 --- a/bin/start_staging +++ b/bin/start_staging @@ -174,6 +174,7 @@ fetch_env_variables() { export SECURED_STORAGE_BUCKET=$(echo "$SECRET_JSON" | jq -r '.secured_storage_bucket') export SECURED_STORAGE_REGION=$(echo "$SECRET_JSON" | jq -r '.secured_storage_region') export ENCRYPTION_SECRET=$(echo "$SECRET_JSON" | jq -r '.ENCRYPTION_SECRET // empty') + export CAREERPLUG_WEBHOOK_SECRET=$(echo "$SECRET_JSON" | jq -r '.CAREERPLUG_WEBHOOK_SECRET // empty') # Validate that we got the values @@ -239,6 +240,12 @@ fetch_env_variables() { echo "✓ ENCRYPTION_SECRET written to .env.staging" fi + # Add CareerPlug webhook secret if it exists + if [ -n "$CAREERPLUG_WEBHOOK_SECRET" ]; then + echo "CAREERPLUG_WEBHOOK_SECRET=$CAREERPLUG_WEBHOOK_SECRET" >> ./.env.staging + echo "✓ CAREERPLUG_WEBHOOK_SECRET written to .env.staging" + fi + echo "✓ Environment variables successfully retrieved and written to .env.staging" } From a6b11703a26c8ddedad34a3a7b4427b2c9644064 Mon Sep 17 00:00:00 2001 From: Bernardo Anderson Date: Wed, 4 Feb 2026 22:39:30 -0600 Subject: [PATCH 2/7] CP-11565 - Rubocop fix --- app/models/account.rb | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/app/models/account.rb b/app/models/account.rb index d397efd2e..52d09ba37 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -59,18 +59,6 @@ class Account < ApplicationRecord after_create :create_careerplug_webhook - private - - def create_careerplug_webhook - return unless ENV['CAREERPLUG_WEBHOOK_SECRET'].present? - - webhook_urls.create!( - url: 'https://www.careerplug.com/api/docuseal/events', - events: %w[form.viewed form.started form.completed form.declined], - secret: { 'X-CareerPlug-Secret' => ENV.fetch('CAREERPLUG_WEBHOOK_SECRET') } - ) - end - scope :active, -> { where(archived_at: nil) } def self.find_or_create_by_external_id(external_id, name, attributes = {}) @@ -86,4 +74,16 @@ def default_template_folder super || build_default_template_folder(name: TemplateFolder::DEFAULT_NAME, author_id: users.minimum(:id)).tap(&:save!) end + + private + + def create_careerplug_webhook + return if ENV['CAREERPLUG_WEBHOOK_SECRET'].blank? + + webhook_urls.create!( + url: 'https://www.careerplug.com/api/docuseal/events', + events: %w[form.viewed form.started form.completed form.declined], + secret: { 'X-CareerPlug-Secret' => ENV.fetch('CAREERPLUG_WEBHOOK_SECRET') } + ) + end end From d8115dc59c710d34782435312f4f501c80c1f0db Mon Sep 17 00:00:00 2001 From: Bernardo Anderson Date: Mon, 9 Feb 2026 16:57:48 -0600 Subject: [PATCH 3/7] CP-11565 - Make it easier to configure webhooks --- lib/tasks/webhooks.rake | 57 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 lib/tasks/webhooks.rake diff --git a/lib/tasks/webhooks.rake b/lib/tasks/webhooks.rake new file mode 100644 index 000000000..cd892c335 --- /dev/null +++ b/lib/tasks/webhooks.rake @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +namespace :webhooks do + desc 'Configure CareerPlug webhook secret from CAREERPLUG_WEBHOOK_SECRET env var' + task configure_careerplug: :environment do + secret = ENV.fetch('CAREERPLUG_WEBHOOK_SECRET') do + if Rails.env.development? + 'development_webhook_secret' + else + abort 'CAREERPLUG_WEBHOOK_SECRET environment variable is required' + end + end + + webhook_urls = WebhookUrl.where('url LIKE ? OR url LIKE ? OR url LIKE ?', + '%careerplug%', '%cpats%', '%localhost:3000%') + + if webhook_urls.any? + webhook_urls.find_each do |webhook_url| + webhook_url.update!(secret: { 'X-CareerPlug-Secret' => secret }) + puts "Updated webhook secret for #{webhook_url.url}" + end + puts "Updated #{webhook_urls.count} webhook URL(s)" + else + puts 'No CareerPlug webhook URLs found. Available webhooks:' + WebhookUrl.find_each { |w| puts " - #{w.id}: #{w.url}" } + end + end + + desc 'Set up development webhook URLs for all accounts (creates URLs + configures secret)' + task setup_development: :environment do + abort 'This task is only for development' unless Rails.env.development? + + url = 'http://localhost:3000/api/docuseal/events' + secret = { 'X-CareerPlug-Secret' => 'development_webhook_secret' } + events = %w[form.viewed form.started form.completed form.declined] + + created = 0 + updated = 0 + + Account.find_each do |account| + webhook_url = WebhookUrl.find_or_initialize_by(account: account, sha1: Digest::SHA1.hexdigest(url)) + + if webhook_url.new_record? + webhook_url.assign_attributes(url: url, events: events, secret: secret) + webhook_url.save! + created += 1 + puts "Created webhook URL for account #{account.id}: #{account.name}" + elsif webhook_url.secret != secret + webhook_url.update!(secret: secret) + updated += 1 + puts "Updated webhook secret for account #{account.id}: #{account.name}" + end + end + + puts "Done: #{created} created, #{updated} updated" + end +end From 76e4fdb5f2ad0901a658842d09d22395142fb3c7 Mon Sep 17 00:00:00 2001 From: Bernardo Anderson Date: Mon, 16 Feb 2026 13:24:20 -0600 Subject: [PATCH 4/7] CP-11565 - Make CareerPlug webhook URL configurable Load dotenv in development for environment variable access --- app/models/account.rb | 2 +- config/dotenv.rb | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/models/account.rb b/app/models/account.rb index 52d09ba37..c37fe7993 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -81,7 +81,7 @@ def create_careerplug_webhook return if ENV['CAREERPLUG_WEBHOOK_SECRET'].blank? webhook_urls.create!( - url: 'https://www.careerplug.com/api/docuseal/events', + url: ENV.fetch('CAREERPLUG_WEBHOOK_URL', 'https://www.careerplug.com/api/docuseal/events'), events: %w[form.viewed form.started form.completed form.declined], secret: { 'X-CareerPlug-Secret' => ENV.fetch('CAREERPLUG_WEBHOOK_SECRET') } ) diff --git a/config/dotenv.rb b/config/dotenv.rb index 110119c00..ee98368c2 100644 --- a/config/dotenv.rb +++ b/config/dotenv.rb @@ -1,5 +1,10 @@ # frozen_string_literal: true +if ENV['RAILS_ENV'] == 'development' + require 'dotenv' + Dotenv.load('.env') +end + if ENV['RAILS_ENV'] == 'production' || ENV['RAILS_ENV'] == 'staging' if !ENV['AWS_SECRET_MANAGER_ID'].to_s.empty? require 'aws-sdk-secretsmanager' From e59866b82889f80fcd5a035a358f18970eb0d73e Mon Sep 17 00:00:00 2001 From: Bernardo Anderson Date: Mon, 16 Feb 2026 13:26:42 -0600 Subject: [PATCH 5/7] CP-11565 - Update CareerPlug webhook URL to use ENV fetch without default Removed default URL for CareerPlug webhook configuration to force explicit setting of required environment variable. --- app/models/account.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/account.rb b/app/models/account.rb index c37fe7993..6a9d2a448 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -81,7 +81,7 @@ def create_careerplug_webhook return if ENV['CAREERPLUG_WEBHOOK_SECRET'].blank? webhook_urls.create!( - url: ENV.fetch('CAREERPLUG_WEBHOOK_URL', 'https://www.careerplug.com/api/docuseal/events'), + url: ENV.fetch('CAREERPLUG_WEBHOOK_URL'), events: %w[form.viewed form.started form.completed form.declined], secret: { 'X-CareerPlug-Secret' => ENV.fetch('CAREERPLUG_WEBHOOK_SECRET') } ) From 6981c658c618f1e660de0c4b967a6ff9497a99c7 Mon Sep 17 00:00:00 2001 From: Bernardo Anderson Date: Mon, 16 Feb 2026 17:25:29 -0600 Subject: [PATCH 6/7] CP-11565 - Add CareerPlug webhook vars to env setup Read CAREERPLUG_WEBHOOK_SECRET and CAREERPLUG_WEBHOOK_URL from SECRET_JSON and write them to .env.production and .env.staging across all start scripts; remove old vars with a single regex and only append the new vars when provided --- bin/start_console_production | 28 +++++++++++++++++----------- bin/start_console_staging | 28 +++++++++++++++++----------- bin/start_production | 32 ++++++++++++++++---------------- bin/start_staging | 26 ++++++++++---------------- 4 files changed, 60 insertions(+), 54 deletions(-) diff --git a/bin/start_console_production b/bin/start_console_production index 82f3876d9..699b601e1 100755 --- a/bin/start_console_production +++ b/bin/start_console_production @@ -169,6 +169,8 @@ fetch_env_variables() { export NEWRELIC_APP_NAME=$(echo "$SECRET_JSON" | jq -r '.newrelic_app_name') export NEWRELIC_MONITOR_MODE=$(echo "$SECRET_JSON" | jq -r '.newrelic_monitor_mode') export ENCRYPTION_SECRET=$(echo "$SECRET_JSON" | jq -r '.ENCRYPTION_SECRET // empty') + export CAREERPLUG_WEBHOOK_SECRET=$(echo "$SECRET_JSON" | jq -r '.CAREERPLUG_WEBHOOK_SECRET // empty') + export CAREERPLUG_WEBHOOK_URL=$(echo "$SECRET_JSON" | jq -r '.CAREERPLUG_WEBHOOK_URL // empty') # Validate that we got the values @@ -187,19 +189,11 @@ fetch_env_variables() { # Write variables to .env.production file echo "Writing environment variables to .env.production..." - # Remove existing DB_HOST, REDIS_URL, and S3_ATTACHMENTS_BUCKET lines if they exist + # Remove existing environment variables if they exist if [ -f "./.env.production" ]; then echo "Removing existing variables from .env.production" - grep -v "^DB_HOST=" ./.env.production > ./.env.production.tmp || true - grep -v "^REDIS_URL=" ./.env.production.tmp > ./.env.production || true - grep -v "^S3_ATTACHMENTS_BUCKET=" ./.env.production.tmp > ./.env.production || true - grep -v "^AIRBRAKE_ID=" ./.env.production.tmp > ./.env.production || true - grep -v "^AIRBRAKE_KEY=" ./.env.production.tmp > ./.env.production || true - grep -v "^NEWRELIC_LICENSE_KEY=" ./.env.production.tmp > ./.env.production || true - grep -v "^NEWRELIC_APP_NAME=" ./.env.production.tmp > ./.env.production || true - grep -v "^NEWRELIC_MONITOR_MODE=" ./.env.production.tmp > ./.env.production || true - grep -v "^ENCRYPTION_SECRET=" ./.env.production.tmp > ./.env.production || true - rm -f ./.env.production.tmp + grep -Ev "^(DB_HOST|REDIS_URL|S3_ATTACHMENTS_BUCKET|AIRBRAKE_ID|AIRBRAKE_KEY|NEWRELIC_LICENSE_KEY|NEWRELIC_APP_NAME|NEWRELIC_MONITOR_MODE|ENCRYPTION_SECRET|CAREERPLUG_WEBHOOK_SECRET|CAREERPLUG_WEBHOOK_URL)=" ./.env.production > ./.env.production.tmp || true + mv ./.env.production.tmp ./.env.production fi # Append the new credentials @@ -218,6 +212,18 @@ fetch_env_variables() { echo "✓ ENCRYPTION_SECRET written to .env.production" fi + # Add CareerPlug webhook secret if it exists + if [ -n "$CAREERPLUG_WEBHOOK_SECRET" ]; then + echo "CAREERPLUG_WEBHOOK_SECRET=$CAREERPLUG_WEBHOOK_SECRET" >> ./.env.production + echo "✓ CAREERPLUG_WEBHOOK_SECRET written to .env.production" + fi + + # Add CareerPlug webhook URL if it exists + if [ -n "$CAREERPLUG_WEBHOOK_URL" ]; then + echo "CAREERPLUG_WEBHOOK_URL=$CAREERPLUG_WEBHOOK_URL" >> ./.env.production + echo "✓ CAREERPLUG_WEBHOOK_URL written to .env.production" + fi + echo "✓ Environment variables successfully retrieved and written to .env.production" } diff --git a/bin/start_console_staging b/bin/start_console_staging index d7bdca8e8..f5e58684a 100755 --- a/bin/start_console_staging +++ b/bin/start_console_staging @@ -169,6 +169,8 @@ fetch_env_variables() { export NEWRELIC_APP_NAME=$(echo "$SECRET_JSON" | jq -r '.newrelic_app_name') export NEWRELIC_MONITOR_MODE=$(echo "$SECRET_JSON" | jq -r '.newrelic_monitor_mode') export ENCRYPTION_SECRET=$(echo "$SECRET_JSON" | jq -r '.ENCRYPTION_SECRET // empty') + export CAREERPLUG_WEBHOOK_SECRET=$(echo "$SECRET_JSON" | jq -r '.CAREERPLUG_WEBHOOK_SECRET // empty') + export CAREERPLUG_WEBHOOK_URL=$(echo "$SECRET_JSON" | jq -r '.CAREERPLUG_WEBHOOK_URL // empty') # Validate that we got the values if [ "$DB_HOST" = "null" ] || [ "$REDIS_URL" = "null" ] || [ "$S3_ATTACHMENTS_BUCKET" = "null" ] || [ -z "$DB_HOST" ] || [ -z "$REDIS_URL" ] || [ -z "$S3_ATTACHMENTS_BUCKET" ]; then @@ -186,19 +188,11 @@ fetch_env_variables() { # Write variables to .env.staging file echo "Writing environment variables to .env.staging..." - # Remove existing DB_HOST, REDIS_URL, and S3_ATTACHMENTS_BUCKET lines if they exist + # Remove existing environment variables if they exist if [ -f "./.env.staging" ]; then echo "Removing existing variables from .env.staging" - grep -v "^DB_HOST=" ./.env.staging > ./.env.staging.tmp || true - grep -v "^REDIS_URL=" ./.env.staging.tmp > ./.env.staging || true - grep -v "^S3_ATTACHMENTS_BUCKET=" ./.env.staging.tmp > ./.env.staging || true - grep -v "^AIRBRAKE_ID=" ./.env.staging.tmp > ./.env.staging || true - grep -v "^AIRBRAKE_KEY=" ./.env.staging.tmp > ./.env.staging || true - grep -v "^NEWRELIC_LICENSE_KEY=" ./.env.staging.tmp > ./.env.staging || true - grep -v "^NEWRELIC_APP_NAME=" ./.env.staging.tmp > ./.env.staging || true - grep -v "^NEWRELIC_MONITOR_MODE=" ./.env.staging.tmp > ./.env.staging || true - grep -v "^ENCRYPTION_SECRET=" ./.env.staging.tmp > ./.env.staging || true - rm -f ./.env.staging.tmp + grep -Ev "^(DB_HOST|REDIS_URL|S3_ATTACHMENTS_BUCKET|AIRBRAKE_ID|AIRBRAKE_KEY|NEWRELIC_LICENSE_KEY|NEWRELIC_APP_NAME|NEWRELIC_MONITOR_MODE|ENCRYPTION_SECRET|CAREERPLUG_WEBHOOK_SECRET|CAREERPLUG_WEBHOOK_URL)=" ./.env.staging > ./.env.staging.tmp || true + mv ./.env.staging.tmp ./.env.staging fi # Append the new credentials @@ -217,6 +211,18 @@ fetch_env_variables() { echo "✓ ENCRYPTION_SECRET written to .env.staging" fi + # Add CareerPlug webhook secret if it exists + if [ -n "$CAREERPLUG_WEBHOOK_SECRET" ]; then + echo "CAREERPLUG_WEBHOOK_SECRET=$CAREERPLUG_WEBHOOK_SECRET" >> ./.env.staging + echo "✓ CAREERPLUG_WEBHOOK_SECRET written to .env.staging" + fi + + # Add CareerPlug webhook URL if it exists + if [ -n "$CAREERPLUG_WEBHOOK_URL" ]; then + echo "CAREERPLUG_WEBHOOK_URL=$CAREERPLUG_WEBHOOK_URL" >> ./.env.staging + echo "✓ CAREERPLUG_WEBHOOK_URL written to .env.staging" + fi + echo "✓ Environment variables successfully retrieved and written to .env.staging" } diff --git a/bin/start_production b/bin/start_production index 626ede920..fa60e7da2 100755 --- a/bin/start_production +++ b/bin/start_production @@ -128,6 +128,12 @@ fetch_allowed_hosts() { exit 1 fi + # Remove existing ALLOWED_HOSTS line if it exists + if [ -f "./.env.production" ]; then + grep -v "^ALLOWED_HOSTS=" ./.env.production > ./.env.production.tmp || true + mv ./.env.production.tmp ./.env.production + fi + # Write allowed hosts to .env.production file echo "Writing allowed hosts to .env.production..." echo "ALLOWED_HOSTS=$ALLOWED_HOSTS" >> ./.env.production @@ -171,6 +177,7 @@ fetch_env_variables() { export SECURED_STORAGE_REGION=$(echo "$SECRET_JSON" | jq -r '.secured_storage_region') export ENCRYPTION_SECRET=$(echo "$SECRET_JSON" | jq -r '.ENCRYPTION_SECRET // empty') export CAREERPLUG_WEBHOOK_SECRET=$(echo "$SECRET_JSON" | jq -r '.CAREERPLUG_WEBHOOK_SECRET // empty') + export CAREERPLUG_WEBHOOK_URL=$(echo "$SECRET_JSON" | jq -r '.CAREERPLUG_WEBHOOK_URL // empty') # Validate that we got the values if [ "$DB_HOST" = "null" ] || [ "$REDIS_URL" = "null" ] || [ "$S3_ATTACHMENTS_BUCKET" = "null" ] || [ -z "$DB_HOST" ] || [ -z "$REDIS_URL" ] || [ -z "$S3_ATTACHMENTS_BUCKET" ]; then @@ -194,24 +201,11 @@ fetch_env_variables() { # Write variables to .env.production file echo "Writing environment variables to .env.production..." - # Remove existing DB_HOST, REDIS_URL, and S3_ATTACHMENTS_BUCKET lines if they exist + # Remove existing environment variables if they exist if [ -f "./.env.production" ]; then echo "Removing existing variables from .env.production" - grep -v "^DB_HOST=" ./.env.production > ./.env.production.tmp || true - grep -v "^REDIS_URL=" ./.env.production.tmp > ./.env.production || true - grep -v "^S3_ATTACHMENTS_BUCKET=" ./.env.production.tmp > ./.env.production || true - grep -v "^AIRBRAKE_ID=" ./.env.production.tmp > ./.env.production || true - grep -v "^AIRBRAKE_KEY=" ./.env.production.tmp > ./.env.production || true - grep -v "^NEWRELIC_LICENSE_KEY=" ./.env.production.tmp > ./.env.production || true - grep -v "^NEWRELIC_APP_NAME=" ./.env.production.tmp > ./.env.production || true - grep -v "^NEWRELIC_MONITOR_MODE=" ./.env.production.tmp > ./.env.production || true - grep -v "^CF_URL=" ./.env.production.tmp > ./.env.production || true - grep -v "^CF_KEY_PAIR_ID=" ./.env.production.tmp > ./.env.production || true - grep -v "^CF_KEY_SECRET=" ./.env.production.tmp > ./.env.production || true - grep -v "^SECURED_STORAGE_BUCKET=" ./.env.production.tmp > ./.env.production || true - grep -v "^SECURED_STORAGE_REGION=" ./.env.production.tmp > ./.env.production || true - grep -v "^ENCRYPTION_SECRET=" ./.env.production.tmp > ./.env.production || true - rm -f ./.env.production.tmp + grep -Ev "^(DB_HOST|REDIS_URL|S3_ATTACHMENTS_BUCKET|AIRBRAKE_ID|AIRBRAKE_KEY|NEWRELIC_LICENSE_KEY|NEWRELIC_APP_NAME|NEWRELIC_MONITOR_MODE|CF_URL|CF_KEY_PAIR_ID|CF_KEY_SECRET|SECURED_STORAGE_BUCKET|SECURED_STORAGE_REGION|ENCRYPTION_SECRET|CAREERPLUG_WEBHOOK_SECRET|CAREERPLUG_WEBHOOK_URL)=" ./.env.production > ./.env.production.tmp || true + mv ./.env.production.tmp ./.env.production fi # Append the new credentials @@ -241,6 +235,12 @@ fetch_env_variables() { echo "✓ CAREERPLUG_WEBHOOK_SECRET written to .env.production" fi + # Add CareerPlug webhook URL if it exists + if [ -n "$CAREERPLUG_WEBHOOK_URL" ]; then + echo "CAREERPLUG_WEBHOOK_URL=$CAREERPLUG_WEBHOOK_URL" >> ./.env.production + echo "✓ CAREERPLUG_WEBHOOK_URL written to .env.production" + fi + echo "✓ Environment variables successfully retrieved and written to .env.production" } diff --git a/bin/start_staging b/bin/start_staging index 176c2a26d..f41f19e07 100755 --- a/bin/start_staging +++ b/bin/start_staging @@ -175,6 +175,7 @@ fetch_env_variables() { export SECURED_STORAGE_REGION=$(echo "$SECRET_JSON" | jq -r '.secured_storage_region') export ENCRYPTION_SECRET=$(echo "$SECRET_JSON" | jq -r '.ENCRYPTION_SECRET // empty') export CAREERPLUG_WEBHOOK_SECRET=$(echo "$SECRET_JSON" | jq -r '.CAREERPLUG_WEBHOOK_SECRET // empty') + export CAREERPLUG_WEBHOOK_URL=$(echo "$SECRET_JSON" | jq -r '.CAREERPLUG_WEBHOOK_URL // empty') # Validate that we got the values @@ -199,24 +200,11 @@ fetch_env_variables() { # Write variables to .env.staging file echo "Writing environment variables to .env.staging..." - # Remove existing DB_HOST, REDIS_URL, and S3_ATTACHMENTS_BUCKET lines if they exist + # Remove existing environment variables if they exist if [ -f "./.env.staging" ]; then echo "Removing existing variables from .env.staging" - grep -v "^DB_HOST=" ./.env.staging > ./.env.staging.tmp || true - grep -v "^REDIS_URL=" ./.env.staging.tmp > ./.env.staging || true - grep -v "^S3_ATTACHMENTS_BUCKET=" ./.env.staging.tmp > ./.env.staging || true - grep -v "^AIRBRAKE_ID=" ./.env.staging.tmp > ./.env.staging || true - grep -v "^AIRBRAKE_KEY=" ./.env.staging.tmp > ./.env.staging || true - grep -v "^NEWRELIC_LICENSE_KEY=" ./.env.staging.tmp > ./.env.staging || true - grep -v "^NEWRELIC_APP_NAME=" ./.env.staging.tmp > ./.env.staging || true - grep -v "^NEWRELIC_MONITOR_MODE=" ./.env.staging.tmp > ./.env.staging || true - grep -v "^CF_URL=" ./.env.staging.tmp > ./.env.staging || true - grep -v "^CF_KEY_PAIR_ID=" ./.env.staging.tmp > ./.env.staging || true - grep -v "^CF_KEY_SECRET=" ./.env.staging.tmp > ./.env.staging || true - grep -v "^SECURED_STORAGE_BUCKET=" ./.env.staging.tmp > ./.env.staging || true - grep -v "^SECURED_STORAGE_REGION=" ./.env.staging.tmp > ./.env.staging || true - grep -v "^ENCRYPTION_SECRET=" ./.env.staging.tmp > ./.env.staging || true - rm -f ./.env.staging.tmp + grep -Ev "^(DB_HOST|REDIS_URL|S3_ATTACHMENTS_BUCKET|AIRBRAKE_ID|AIRBRAKE_KEY|NEWRELIC_LICENSE_KEY|NEWRELIC_APP_NAME|NEWRELIC_MONITOR_MODE|CF_URL|CF_KEY_PAIR_ID|CF_KEY_SECRET|SECURED_STORAGE_BUCKET|SECURED_STORAGE_REGION|ENCRYPTION_SECRET|CAREERPLUG_WEBHOOK_SECRET|CAREERPLUG_WEBHOOK_URL)=" ./.env.staging > ./.env.staging.tmp || true + mv ./.env.staging.tmp ./.env.staging fi # Append the new credentials @@ -246,6 +234,12 @@ fetch_env_variables() { echo "✓ CAREERPLUG_WEBHOOK_SECRET written to .env.staging" fi + # Add CareerPlug webhook URL if it exists + if [ -n "$CAREERPLUG_WEBHOOK_URL" ]; then + echo "CAREERPLUG_WEBHOOK_URL=$CAREERPLUG_WEBHOOK_URL" >> ./.env.staging + echo "✓ CAREERPLUG_WEBHOOK_URL written to .env.staging" + fi + echo "✓ Environment variables successfully retrieved and written to .env.staging" } From f8a566604289b989bff4182b0cc01283ce888f22 Mon Sep 17 00:00:00 2001 From: Bernardo Anderson Date: Mon, 16 Feb 2026 17:28:58 -0600 Subject: [PATCH 7/7] Change account webhook creation from after_create to after_commit This ensures that CareerPlug webhooks are only created after the account transaction has been successfully committed to the database. This prevents orphaned webhook records if account creation fails or is rolled back. The change improves data consistency and follows Rails best practices for callbacks that create associated records or have external side effects. Includes tests to verify that: - Webhooks are created after successful account creation - Webhooks are not created if account creation fails/rolls back - Webhooks are not created when CAREERPLUG_WEBHOOK_SECRET is blank --- app/models/account.rb | 2 +- .../account_create_careerplug_webhook_spec.rb | 65 +++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 spec/models/account_create_careerplug_webhook_spec.rb diff --git a/app/models/account.rb b/app/models/account.rb index 6a9d2a448..e9acfa12f 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -57,7 +57,7 @@ class Account < ApplicationRecord validates :external_account_id, uniqueness: true, allow_nil: true - after_create :create_careerplug_webhook + after_commit :create_careerplug_webhook, on: :create scope :active, -> { where(archived_at: nil) } diff --git a/spec/models/account_create_careerplug_webhook_spec.rb b/spec/models/account_create_careerplug_webhook_spec.rb new file mode 100644 index 000000000..140a9792a --- /dev/null +++ b/spec/models/account_create_careerplug_webhook_spec.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Account, '#create_careerplug_webhook' do + around do |example| + original_secret = ENV.fetch('CAREERPLUG_WEBHOOK_SECRET', nil) + original_url = ENV.fetch('CAREERPLUG_WEBHOOK_URL', nil) + + # Set required env vars for webhook creation + ENV['CAREERPLUG_WEBHOOK_SECRET'] = 'test_secret' + ENV['CAREERPLUG_WEBHOOK_URL'] = 'http://example.com/webhook' + + example.run + + # Restore original env vars + ENV['CAREERPLUG_WEBHOOK_SECRET'] = original_secret + ENV['CAREERPLUG_WEBHOOK_URL'] = original_url + end + + describe 'CareerPlug webhook creation' do + it 'creates webhook after successful account creation' do + account = build(:account) + expect(account.webhook_urls).to be_empty + + account.save! + + expect(account.webhook_urls.count).to eq(1) + webhook = account.webhook_urls.first + expect(webhook.url).to eq('http://example.com/webhook') + expect(webhook.events).to eq(['form.viewed', 'form.started', 'form.completed', 'form.declined']) + expect(webhook.secret).to eq({ 'X-CareerPlug-Secret' => 'test_secret' }) + end + + it 'does not create webhook if account creation fails' do + # This test verifies that after_commit behavior works correctly + # by simulating a transaction rollback + + expect do + described_class.transaction do + create(:account) + # Simulate some error that would cause rollback + raise ActiveRecord::Rollback + end + end.not_to change(described_class, :count) + + expect do + described_class.transaction do + create(:account) + raise ActiveRecord::Rollback + end + end.not_to change(WebhookUrl, :count) + end + + it 'does not create webhook when CAREERPLUG_WEBHOOK_SECRET is blank' do + original_secret = ENV.fetch('CAREERPLUG_WEBHOOK_SECRET', nil) + ENV['CAREERPLUG_WEBHOOK_SECRET'] = '' + + account = create(:account) + expect(account.webhook_urls.count).to eq(0) + + ENV['CAREERPLUG_WEBHOOK_SECRET'] = original_secret + end + end +end