docs/ads-publishing-production-launch.md.Ads Publishing Production Launch
This runbook covers the staging-first launch for direct Meta/Facebook Ads and Google Ads publishing. The system creates external campaigns paused by default. Live spend starts only after approval, budget cap validation, and explicit activation.
Version Checks
- Meta: verify
META_GRAPH_API_VERSIONagainst the official Meta Marketing API changelog immediately before staging and production rollout: https://developers.facebook.com/docs/marketing-api/ - Google Ads: verify
GOOGLE_ADS_API_VERSIONagainst the official release and sunset tables. As of the June 2026 implementation check,v24.1is the released default andv24.2is listed as a June 2026 upcoming release: https://developers.google.com/google-ads/api/docs/sunset-dates - Keep both versions environment-configurable. Do not hard-code provider API versions in code.
Required Environment
Add the following values to staging and production:
META_CLIENT_ID=
META_CLIENT_SECRET=
META_GRAPH_API_VERSION=v25.0
GOOGLE_ADS_CLIENT_ID=
GOOGLE_ADS_CLIENT_SECRET=
GOOGLE_ADS_DEVELOPER_TOKEN=
GOOGLE_ADS_MANAGER_CUSTOMER_ID=
GOOGLE_ADS_API_VERSION=v24.1
ADS_PUBLISHING_ENABLED=true
ADS_ACTIVATION_ENABLED=true
ADS_SANDBOX_MUTATION_ENABLED=false
ADS_AUTO_PAUSE_ENABLED=false
ADS_OFFLINE_CONVERSIONS_ENABLED=false
ADS_TRACKING_CHECKS_ENABLED=true
QUEUE_CONNECTION=sync is acceptable only for local/demo. Production must run a real queue backend and worker before scheduled activation is enabled.
Provider Readiness
Meta/Facebook Ads:
- App review completed for
ads_management. - Existing read scopes still available:
ads_read,leads_retrieval, page discovery/read permissions. - Connected provider account has a valid access token.
- Page ID is valid and accessible.
- Ad account is billing-ready and permitted for campaign, ad set, creative, and ad mutations.
Google Ads:
- OAuth client ID and secret configured.
- Developer token approved for the intended environment.
- Manager customer ID configured when required.
- Customer ID access is confirmed.
- Search campaign, budget, ad group, keyword, and responsive search ad mutate calls are allowed.
Deployment Steps
Run these steps on staging first:
php artisan migrate --force
php artisan optimize:clear
php artisan crm:sync-ad-account-readiness
php artisan crm:sync-ad-campaigns
php artisan crm:process-ad-activations
php artisan crm:ads-reconcile-attribution
php artisan crm:ads-check-tracking
php artisan crm:ads-monitor-spend
php artisan crm:ads-health-summary
Then verify:
- Queue worker is running for the production queue connection.
- Scheduler cron or task scheduler is invoking Laravel schedule every minute.
- System Health shows recent successful runs for:
crm:process-ad-activationscrm:sync-ad-campaignscrm:sync-ad-account-readinesscrm:ads-reconcile-attributioncrm:ads-check-trackingcrm:ads-monitor-spendcrm:ads-retry-failed-jobscrm:ads-health-summary- Budget caps exist for each production provider/account/currency or the default caps are acceptable.
Budget Guardrails
Default caps are:
- Daily: PKR 50,000
- Lifetime: PKR 500,000
Admins with manage ad campaign budgets can set provider/account/currency policy caps from the ads cockpit. Activation revalidates caps before any external status mutation.
Production kill switches:
ADS_PUBLISHING_ENABLED=falseblocks external publish queueing.ADS_ACTIVATION_ENABLED=falseblocks activation requests and activation queueing.ADS_SANDBOX_MUTATION_ENABLED=falseblocks sandbox smoke activation/pause mutations in production.ADS_AUTO_PAUSE_ENABLED=falsekeeps automatic pause disabled unless operations intentionally turns it on and saves auto-pause policies.ADS_OFFLINE_CONVERSIONS_ENABLED=falseblocks offline conversion upload until tracking readiness and provider credentials are verified.ADS_TRACKING_CHECKS_ENABLED=truekeeps tracking readiness checks available from commands and cockpit actions.
Read-only sync, health summary, tracking checks, and audit export remain available when publishing or activation is disabled.
Sandbox Smoke
Run a read-only/paused smoke first:
php artisan crm:ads-sandbox-smoke --provider=facebook_meta --campaign=1
php artisan crm:ads-sandbox-smoke --provider=google_ads --campaign=1
Non-production activation/pause smoke:
php artisan crm:ads-sandbox-smoke --provider=facebook_meta --campaign=1 --activate-and-pause
php artisan crm:ads-sandbox-smoke --provider=google_ads --campaign=1 --activate-and-pause
Production activation/pause smoke requires ADS_SANDBOX_MUTATION_ENABLED=true.
Attribution and Audit
Reconcile campaign attribution after provider sync:
php artisan crm:ads-reconcile-attribution --campaign=1 --explain
php artisan crm:ads-check-tracking --campaign=1
php artisan crm:ads-monitor-spend --campaign=1
Export audit evidence:
php artisan crm:ads-export-audit --campaign=1 --path=ads/audit-campaign-1.json
php artisan crm:ads-health-summary --campaign=1 --path=ads/health-campaign-1.json
Offline conversion upload stays disabled by default. After provider credentials and tracking readiness pass, enable ADS_OFFLINE_CONVERSIONS_ENABLED=true and run:
php artisan crm:ads-upload-offline-conversions --campaign=1
Retry failed provider jobs without duplicating external mappings:
php artisan crm:ads-retry-failed-jobs --campaign=1
Production Smoke Checklist
Run the full path in staging for both Meta and Google:
- Create a CRM campaign.
- Create an ad draft.
- Run readiness.
- Validate the draft.
- Publish paused and confirm external mappings are paused.
- Request activation approval.
- Approve activation.
- Activate immediately or schedule activation.
- Sync status and performance.
- Use Emergency Pause and confirm external mappings return to paused.
Fail launch if activation bypasses approval, external mappings duplicate after retry, dark mode shows broken light containers, command health is stale, or sandbox provider mutations fail.