Microsoft Ads Conversion Tracking Setup

The Microsoft Ads conversion upload service is already deployed and ready. This guide covers the steps to enable it.

What's Already Built

Step 1: Create Conversion Goals in Microsoft Ads

  1. Log into Microsoft Advertising
  2. Go to Tools > Conversion tracking > Conversion goals
  3. Create 3 offline conversion goals:
Goal Name Type Revenue Value
Lead Offline conversion Each conversion action has the same value $7.00
Quote Offline conversion Each conversion action has the same value $20.00
Purchase Offline conversion Conversion action value may vary (dynamic)
  1. Note the Goal IDs for each (you'll use these for goal names in env vars)

Step 2: Register an Application for API Access

  1. Go to Azure Portal
  2. Navigate to Azure Active Directory > App registrations > New registration
  3. Set:
  4. After creation, note the Application (client) ID
  5. Go to Certificates & secrets > New client secret
  6. Note the Client secret value (save immediately, shown only once)

Step 3: Get Developer Token

  1. Go to Microsoft Advertising Developer Portal
  2. Sign in with your Microsoft Advertising account
  3. Request a developer token (may require approval for production)
  4. Note the Developer token

Step 4: Get Account and Customer IDs

  1. In Microsoft Advertising, click the gear icon > Accounts & Billing
  2. Note your Account ID (the ad account number)
  3. Note your Customer ID (visible in the URL or account settings)

Step 5: Generate Refresh Token

Run this in Rails console to start OAuth flow:

# Generate authorization URL
client_id = "YOUR_CLIENT_ID"
redirect_uri = "https://admin.quarryrents.com/oauth/microsoft/callback"
scope = "https://ads.microsoft.com/msads.manage offline_access"

auth_url = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?" +
  "client_id=#{client_id}&" +
  "response_type=code&" +
  "redirect_uri=#{CGI.escape(redirect_uri)}&" +
  "scope=#{CGI.escape(scope)}"

puts auth_url
  1. Visit the URL and authorize
  2. You'll be redirected with a code parameter
  3. Exchange the code for tokens:
require 'net/http'
require 'uri'
require 'json'

code = "THE_CODE_FROM_REDIRECT"
client_id = "YOUR_CLIENT_ID"
client_secret = "YOUR_CLIENT_SECRET"
redirect_uri = "https://admin.quarryrents.com/oauth/microsoft/callback"

uri = URI("https://login.microsoftonline.com/common/oauth2/v2.0/token")
response = Net::HTTP.post_form(uri, {
  client_id: client_id,
  client_secret: client_secret,
  code: code,
  redirect_uri: redirect_uri,
  grant_type: "authorization_code",
  scope: "https://ads.microsoft.com/msads.manage offline_access"
})

tokens = JSON.parse(response.body)
puts "Refresh Token: #{tokens['refresh_token']}"
  1. Save the refresh_token - it's long-lived and used for API calls

Step 6: Configure Environment Variables

Add to .kamal/secrets:

# Microsoft Ads API
export MICROSOFT_ADS_CLIENT_ID="your-azure-app-client-id"
export MICROSOFT_ADS_CLIENT_SECRET="your-azure-app-client-secret"
export MICROSOFT_ADS_REFRESH_TOKEN="your-refresh-token"
export MICROSOFT_ADS_DEVELOPER_TOKEN="your-developer-token"
export MICROSOFT_ADS_ACCOUNT_ID="your-account-id"
export MICROSOFT_ADS_CUSTOMER_ID="your-customer-id"
export MICROSOFT_ADS_LEAD_GOAL_NAME="Lead"
export MICROSOFT_ADS_QUOTE_GOAL_NAME="Quote"
export MICROSOFT_ADS_PURCHASE_GOAL_NAME="Purchase"

Step 7: Deploy

source .kamal/secrets && kamal deploy

Step 8: Test

After deploying, test with a conversion that has an msclkid:

# In Rails console
conversion = Conversion.find_by(msclkid: "some_msclkid")
conversion.reset_microsoft_ads_for_retry!
PushConversionToMicrosoftAdsJob.perform_now(conversion.id)

Check the logs for success/failure messages.

Troubleshooting

"credentials_missing" status

The service gracefully skips if any required env var is missing. Check all env vars are set.

OAuth token expired

Refresh tokens are long-lived but can expire. Re-run the OAuth flow in Step 5 if needed.

API errors

Check conversion.microsoft_ads_error for error details. Common issues:

Retroactive Uploads

The system has been capturing msclkid from landing URLs. To push historical conversions:

# Find conversions with msclkid that haven't been pushed
Conversion.where.not(msclkid: [nil, ""]).where(microsoft_ads_status: "skipped").find_each do |c|
  c.reset_microsoft_ads_for_retry!
  PushConversionToMicrosoftAdsJob.perform_later(c.id)
end

Related Files