Build a Serverless Billing Reconciliation Pipeline with Amazon Athena and Amazon Quick

Use Case and Problem

Enterprise billing operations teams processing hundreds of thousands of transactions monthly face a persistent challenge: validating transaction files before they flow into downstream invoicing. Analysts manually collect pipe-delimited files from multiple fulfillment systems, verify record counts, detect duplicate transaction IDs, and check for date anomalies — all under tight processing windows. A single undetected duplicate (for example, 10,000 duplicate transactions at $25 each) translates to a $250,000 invoicing impact.

This article shows how to build a fully serverless billing reconciliation pipeline using Amazon S3, Amazon Athena, Amazon Quick Sight, and Chat Agent capability in Amazon Quick — reducing daily validation effort from hours to minutes with zero infrastructure to manage.


How to Solve the Problem

Prerequisites

  • An AWS account with access to Amazon S3, Amazon Athena, and Amazon Quick Sight
  • An S3 bucket to store billing transaction files
  • Quick Sight Enterprise edition (required for Chat Agent features)

Solution Architecture

The pipeline uses four AWS services in sequence:

Service Role
Amazon S3 Stores raw billing transaction files from fulfillment systems
Amazon Athena SQL engine that reads S3 files directly via external tables and views
Amazon Quick Sight Imports structured data into SPICE for dashboards
Amazon Quick (Chat Agent) Enables natural language queries for ad-hoc reconciliation

Key design decisions:

  • Flat S3 structure (no partitioning) — simplifies deployment for datasets under 10 GB
  • No Parquet conversion — Athena queries text files directly, trading performance for simplicity
  • View-based parsing — a single SQL view handles header/transaction splitting and joining

Step 1: Prepare the Billing Data

Each billing file is a pipe-delimited .txt file with two record types:

Record Type Prefix Fields
Header 01 record_type | file_created_datetime | total_transaction_count | fulfillment_system_name | filename
Transaction 02 record_type | transaction_id | header_link | fulfillment_system_name | transaction_datetime | service_type | duration | amount | currency | status

Sample data:

01|2026-05-01T11:00:10Z|43|DALLAS-FULFILL-02|BILLING_TXNS_DALLAS_20260501_1100
02|TXN-20260501-1101-DAL|BILLING_TXNS_DALLAS_20260501_1100|DALLAS-FULFILL-02|2026-05-01T10:19:50Z|VOICE|00:28:46|0.219|USD|COMPLETED
02|TXN-20260501-1102-DAL|BILLING_TXNS_DALLAS_20260501_1100|DALLAS-FULFILL-02|2026-05-01T10:30:16Z|VOICE|00:30:41|0.198|USD|ADJUSTED
02|TXN-20260501-1103-DAL|BILLING_TXNS_DALLAS_20260501_1100|DALLAS-FULFILL-02|2026-05-01T10:14:46Z|DATA|00:00:00|3.986|USD|PENDING

Upload files to a flat S3 prefix using the naming convention BILLING_TXNS_{CENTER}_{YYYYMMDD}_{HHMM}.txt:

s3://<your-bucket>/reconciliation/

Step 2: Deploy the Athena Layer

Run these three SQL statements sequentially in the Athena query editor.

Create the database:

CREATE DATABASE IF NOT EXISTS billing_reconciliation;

Create the raw external table:

CREATE EXTERNAL TABLE IF NOT EXISTS billing_reconciliation.billing_raw (
  line STRING
)
ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe'
WITH SERDEPROPERTIES (
  'field.delim' = '\n',
  'serialization.format' = '\n'
)
STORED AS TEXTFILE
LOCATION 's3://<your-bucket>/reconciliation/'
TBLPROPERTIES (
  'skip.header.line.count' = '0'
);

Create the parsed transactions view:

CREATE OR REPLACE VIEW billing_reconciliation.billing_transactions AS
WITH raw_with_meta AS (
  SELECT
    line,
    SUBSTR(line, 1, 2) AS record_type,
    "$path" AS source_file
  FROM billing_reconciliation.billing_raw
  WHERE line IS NOT NULL AND LENGTH(TRIM(line)) > 0
),
headers AS (
  SELECT source_file,
    SPLIT_PART(line, '|', 5) AS filename,
    SPLIT_PART(line, '|', 2) AS file_created_datetime,
    CAST(SPLIT_PART(line, '|', 3) AS INTEGER) AS total_transaction_count,
    SPLIT_PART(line, '|', 4) AS fulfillment_system_name
  FROM raw_with_meta WHERE record_type = '01'
),
transactions AS (
  SELECT source_file,
    SPLIT_PART(line, '|', 2) AS transaction_id,
    SPLIT_PART(line, '|', 3) AS header_link,
    SPLIT_PART(line, '|', 4) AS fulfillment_system_name,
    SPLIT_PART(line, '|', 5) AS transaction_datetime,
    SPLIT_PART(line, '|', 6) AS service_type,
    SPLIT_PART(line, '|', 7) AS duration,
    CAST(SPLIT_PART(line, '|', 8) AS DOUBLE) AS amount,
    SPLIT_PART(line, '|', 9) AS currency,
    SPLIT_PART(line, '|', 10) AS status
  FROM raw_with_meta WHERE record_type = '02'
)
SELECT
  h.filename,
  h.file_created_datetime,
  h.total_transaction_count,
  h.fulfillment_system_name,
  t.transaction_id,
  t.transaction_datetime,
  t.service_type,
  t.duration,
  t.amount,
  t.currency,
  t.status
FROM transactions t
INNER JOIN headers h ON t.source_file = h.source_file;

After creation, you can immediately query structured billing data with standard SQL — no ETL required.


Step 3: Connect Quick Sight to the Data

  1. Grant S3 access: Quick Sight Console → Manage Account → Permissions → AWS Resources → select your S3 bucket

  2. Create Athena dataset: Data source: Athena → Catalog: AwsDataCatalog → Database: billing_reconciliation → View: billing_transactions

  3. Configure SPICE import: Select SPICE for sub-second dashboard performance. Set refresh to daily or on-demand.

Note: If the S3 policy was modified outside Quick Sight (e.g., via Terraform), edit the IAM policy directly: IAM Console → Policies → AWSQuickSightS3Policy → add your bucket ARN to the Resource array.


Step 4: Enrich the Dataset for AI

Calculated fields — parse timestamps for time-series analysis:

transaction_date:   parseDate(transaction_datetime, "yyyy-MM-dd'T'HH:mm:ss'Z'")
file_created_date:  parseDate(file_created_datetime, "yyyy-MM-dd'T'HH:mm:ss'Z'")

Column descriptions — help the Chat Agent understand business meaning:

Column Description
transaction_id Unique identifier for each billing transaction. Same ID in multiple files indicates a duplicate.
filename Unique file identifier. Each file contains transactions from one fulfillment center for one hour window.
fulfillment_system_name Regional processing center that originated the transactions.
status COMPLETED = normal, ADJUSTED = re-billed/modified, PENDING = awaiting processing.
amount Rated charge in USD for the transaction.
service_type Type of billable service: VOICE (calls), DATA (bandwidth), SMS (messages).

Dataset instructions — add to the Instructions panel:

“Each file has one header and multiple transaction records. The filename is unique per file. A transaction_id can appear in multiple files — when it does, it indicates a duplicate transaction. Duplicates within 14 days are normal business adjustments. Duplicates 15 or more days apart are anomalies requiring investigation.”


Step 5: Build the Dashboard

Create a Quick Sight analysis with these visual groups:

KPI tiles: Total Transactions, Total Billed Amount, Unique Files, Duplicate Rate
Volume & Revenue: Bar chart (count by fulfillment center), Donut (by service type), Line chart (daily volume)
Status & Duplicates: Status distribution donut, Duplicate transactions table with conditional formatting (red for days_apart ≥ 15)



Step 6: Enable Natural Language Reconciliation

Enable Chat Agent on the dashboard and point it to the billing_transactions dataset. Your team can now run reconciliation checks in plain language:

Reconciliation Task Natural Language Prompt
File count validation How many files did we receive from each fulfillment center?
Duplicate detection Which transaction IDs appear in more than one file?
Revenue summary What is the total billed amount by service type?
Status audit Show me all ADJUSTED transactions over $1.00
Volume anomalies Which day had the highest transaction volume?
Center comparison Compare average transaction amounts across fulfillment centers


Tips for better results:

  • Use column descriptions and dataset instructions to provide business context
  • Keep prompts specific — “transactions over $1 from Dallas this week” outperforms vague queries
  • For complex custom logic, build a dedicated Chat Agent with domain-specific knowledge

Conclusion

This walkthrough deployed a complete billing reconciliation pipeline — from raw S3 files to natural language queries — using four AWS services with zero servers, no ETL jobs, and no database maintenance. The pattern is reusable for any high-volume, file-based validation workflow where analysts need automated checks and self-service access to their data.

For production deployments (10 GB+), consider adding Parquet conversion via AWS Glue, date/hour partitioning, and EventBridge-triggered SPICE refresh.

Further reading:


Author Bio


Nitish Damodar Chaudhari is a Sr. Solutions Architect at AWS, focused on helping enterprise customers accelerate cloud adoption and unlock business value through data and AI.


Deepak Singh is a Sr. GenAI/ML Specialist Solutions Architect at AWS, helping customers design intelligent data pipelines and AI-powered analytics solutions.

1 Like