Amazon AMS / Automation 12 min read

How to Build Hourly Automated Bidding with Amazon Marketing Stream (AMS → BigQuery → Ads API)

A complete guide to pulling hourly conversion data from Amazon Marketing Stream (AMS), storing it in BigQuery, and automatically adjusting bids via Amazon Ads API based on real-time performance.

By Shahzeb
Updated Jan 2026
How to Build Hourly Automated Bidding with Amazon Marketing Stream (AMS → BigQuery → Ads API)

What is Amazon Marketing Stream (AMS)? #

Amazon Marketing Stream (AMS) is a push-based data stream that delivers advertising metrics in near real-time (hourly or more frequent).

Unlike traditional reporting APIs (which you pull), AMS pushes data to your AWS infrastructure, enabling:

  • Hourly conversion tracking
  • Near real-time performance monitoring
  • Fast reactions to campaign changes

Use case: Monitor conversion rates hourly and automatically adjust bids to maximize ROAS (Return on Ad Spend) without manual intervention.

Architecture Overview #

Here's the automated bidding pipeline:

  1. Amazon Marketing Stream (AMS) → Pushes conversion data to AWS
  2. AWS Kinesis Data Stream → Receives the data
  3. AWS Lambda or Kinesis Firehose → Processes and forwards data
  4. BigQuery Data Transfer → Stores structured data in BigQuery
  5. Python script (scheduled hourly) → Analyzes performance + calculates bid adjustments
  6. Amazon Ads API → Pushes bid updates back to campaigns
AMS to BigQuery to Ads API Architecture

Why this setup? AMS only works on AWS, but you can forward data to BigQuery (GCP) for unified analytics. Bidding logic can run on either AWS Lambda or GCP Cloud Functions.

Step 1: Subscribe to Amazon Marketing Stream #

First, you need to subscribe to AMS and configure it to push data to your AWS account.

Follow Amazon's Amazon Marketing Stream documentation to:

  1. Request AMS access (requires Amazon Ads API access first)
  2. Create an AWS Kinesis Data Stream in your account
  3. Use the AMS API to subscribe to data streams (e.g., campaignLevelPerformance, productLevelPerformance)

Sample AMS subscription API call:

POST https://advertising-api.amazon.com/ams/streams
Authorization: Bearer {access_token}
Amazon-Advertising-API-ClientId: {client_id}

{
  "streamType": "campaignLevelPerformance",
  "destinationArn": "arn:aws:kinesis:us-east-1:123456789012:stream/ams-data"
}
AMS subscription configuration

Step 2: Set Up AWS Infrastructure #

You need several AWS components to receive and process AMS data:

1. Kinesis Data Stream

  • Receives data from AMS
  • Acts as a buffer for real-time processing

2. Lambda Function or Kinesis Firehose

Option A: Lambda Function

  • Processes each AMS record as it arrives
  • Parses JSON, transforms data, sends to BigQuery
  • Good for complex logic or enrichment

Option B: Kinesis Firehose

  • Automatically batches and delivers data
  • Simpler setup, less code
  • Can write to S3, then use BigQuery Data Transfer

Recommended: Use Kinesis Firehose to S3, then BigQuery Data Transfer for simplicity. Use Lambda only if you need custom transformations.

Step 3: Transfer Data to BigQuery #

To get AMS data from AWS to BigQuery, use BigQuery Data Transfer Service:

  1. Kinesis Firehose writes AMS data to an S3 bucket (JSON or Parquet)
  2. Set up a BigQuery Data Transfer from S3
  3. Schedule the transfer to run hourly

Alternative: Direct insertion via Lambda

import json
from google.cloud import bigquery

def lambda_handler(event, context):
    client = bigquery.Client()
    table_id = "project.ams_data.conversions"

    rows = []
    for record in event['Records']:
        data = json.loads(record['kinesis']['data'])
        rows.append({
            "campaign_id": data['campaignId'],
            "conversions": data['conversions14d'],
            "spend": data['cost'],
            "timestamp": data['timestamp']
        })

    errors = client.insert_rows_json(table_id, rows)
    if errors:
        print(f"Errors: {errors}")
BigQuery Data Transfer from S3

Step 4: Create BigQuery Tables #

Design your BigQuery schema to store AMS conversion data:

-- AMS conversions table
CREATE TABLE `project.ams_data.conversions` (
  campaign_id STRING,
  ad_group_id STRING,
  keyword_id STRING,
  impressions INT64,
  clicks INT64,
  conversions INT64,
  spend FLOAT64,
  sales FLOAT64,
  timestamp TIMESTAMP,
  inserted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP()
)
PARTITION BY DATE(timestamp)
CLUSTER BY campaign_id;

Pro tip: Use clustering on campaign_id for faster queries when analyzing specific campaigns.

BigQuery AMS conversions table

Step 5: Write Bidding Logic (Performance Analysis) #

Now create a Python script that:

  1. Queries BigQuery for recent conversion data (last hour or day)
  2. Calculates performance metrics (ROAS, CPA, conversion rate)
  3. Determines bid adjustments based on rules

Sample bidding logic:

from google.cloud import bigquery

def calculate_bid_adjustments():
    client = bigquery.Client()

    # Query last hour's performance
    query = '''
        SELECT
            campaign_id,
            SUM(conversions) as conversions,
            SUM(spend) as spend,
            SUM(sales) as sales,
            SUM(sales) / NULLIF(SUM(spend), 0) as roas
        FROM `project.ams_data.conversions`
        WHERE timestamp >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 HOUR)
        GROUP BY campaign_id
    '''

    results = client.query(query).to_dataframe()

    adjustments = []
    for _, row in results.iterrows():
        campaign_id = row['campaign_id']
        roas = row['roas']

        # Bidding rules
        if roas > 3.0:  # High ROAS → increase bids
            adjustment = 1.15  # +15%
        elif roas < 1.5:  # Low ROAS → decrease bids
            adjustment = 0.85  # -15%
        else:
            adjustment = 1.0  # No change

        adjustments.append({
            'campaign_id': campaign_id,
            'bid_adjustment': adjustment
        })

    return adjustments

Important: Add guardrails to prevent extreme bid changes. Cap adjustments (e.g., max ±20% per hour) and set minimum bid thresholds.

Step 6: Push Bid Updates via Amazon Ads API #

Use the Amazon Ads API to update campaign bids programmatically:

import requests

def update_campaign_bids(adjustments, access_token, profile_id):
    url = "https://advertising-api.amazon.com/v2/sp/campaigns"
    headers = {
        "Authorization": f"Bearer {access_token}",
        "Amazon-Advertising-API-ClientId": "your-client-id",
        "Amazon-Advertising-API-Scope": profile_id,
        "Content-Type": "application/json"
    }

    for adj in adjustments:
        campaign_id = adj['campaign_id']
        new_bid = calculate_new_bid(campaign_id, adj['bid_adjustment'])

        # Update campaign budget
        payload = {
            "campaignId": campaign_id,
            "dailyBudget": new_bid
        }

        response = requests.put(
            f"{url}/{campaign_id}",
            headers=headers,
            json=payload
        )

        if response.status_code == 200:
            print(f"Updated campaign {campaign_id} to ${new_bid}")
        else:
            print(f"Failed: {response.text}")

Pro tip: Log all bid changes to a BigQuery audit table for accountability and analysis.

Step 7: Schedule Hourly Execution #

Deploy your bidding script to run hourly:

Option 1: GCP Cloud Scheduler + Cloud Functions

# Cloud Scheduler cron (every hour)
0 * * * *

Option 2: AWS EventBridge + Lambda

  • Create an EventBridge rule with rate(1 hour)
  • Trigger a Lambda function that runs your bidding logic
Hourly scheduler configuration

Adding Safety Guardrails #

Automated bidding can go wrong. Add these safeguards:

1. Bid Caps

def apply_guardrails(new_bid, old_bid, min_bid=0.50, max_bid=10.0):
    # Max change per hour: ±20%
    max_increase = old_bid * 1.20
    max_decrease = old_bid * 0.80

    new_bid = max(min(new_bid, max_increase), max_decrease)

    # Absolute min/max
    new_bid = max(min(new_bid, max_bid), min_bid)

    return new_bid

2. Manual Override

  • Store "manual override" flags in BigQuery
  • Skip automated adjustments for campaigns with overrides active

3. Dry-Run Mode

DRY_RUN = True  # Set to False when ready

if DRY_RUN:
    print(f"[DRY RUN] Would update campaign {campaign_id} to ${new_bid}")
else:
    update_campaign_bids(adjustments, access_token, profile_id)

4. Alerting

  • Send Slack/email alerts when bids change by > 30%
  • Alert on API errors or failed updates
  • Daily summary reports of all bid changes

Monitoring and Optimization #

Track the performance of your automated bidding system:

Key Metrics to Monitor

  • ROAS improvement: Compare pre/post automation
  • Bid stability: Ensure bids don't oscillate wildly
  • API success rate: Track failed bid updates
  • Cost per conversion: Should trend downward

Build a Dashboard

Visualize in Looker Studio or custom dashboard:

  • Hourly ROAS by campaign
  • Bid change timeline
  • Spend vs. conversions correlation
  • Automated vs. manual performance comparison
Automated bidding monitoring dashboard

Cost and ROI Considerations #

Infrastructure Costs

  • AWS Kinesis: ~$15-30/month (hourly data stream)
  • Lambda/Firehose: ~$5-15/month
  • BigQuery: ~$20-50/month (storage + queries)
  • GCP Scheduler/Functions: Mostly free tier

Total estimated cost: $40-100/month

Expected ROI

  • 5-15% ROAS improvement from faster bid adjustments
  • Eliminates manual bid management (saves hours/week)
  • Scales across hundreds of campaigns without added effort

Breakeven: If you spend $10k+/month on ads, a 5% efficiency gain ($500) pays for the infrastructure 5x over.

What You Can Build Next #

Extend your automated bidding system:

  • Keyword-level bidding: Adjust bids per keyword, not just campaigns
  • Seasonal rules: Apply different strategies during high-traffic periods
  • Competitor tracking: Integrate external data (e.g., Keepa) to adjust bids based on competitor pricing
  • ML-based optimization: Use historical data to train models for smarter bid predictions
  • Multi-marketplace: Sync bidding logic across US, EU, and other regions

Need help implementing this?

Tell me your stack and what you want automated. I'll reply with a simple plan tailored to your needs.

Found this helpful? Share it:

WhatsApp