Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

192 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

GovWin to HubSpot to AWS Partner Central, end-to-end and open-source

CI CodeQL License: Apache-2.0 Python 3.12 Terraform >= 1.11

Open-source pipeline from Deltek GovWin IQ to AWS Partner Central via HubSpot CRM. Serverless on AWS, Terraform-deployed, Apache 2.0. No paid connector dependencies.

Built and maintained by Pandora Cloud.

Who this is for (and not for)

Built for: federal AWS partners who use HubSpot CRM and have a Deltek GovWin IQ subscription, and who need to submit opportunities into AWS Partner Central's ACE program. Strongest fit if you're a registered AWS Partner with at least one Approved Solution.

Not built for: organizations using Salesforce, Pipedrive, Microsoft Dynamics, Zoho, or any other CRM (those adapters are explicitly out of scope; see CONTRIBUTING.md). Not built for commercial-only co-sell flows; the project's mapping table is federal-aware (NAICS codes, GovWin opportunity types, federal pipeline stages).

What This Does

Federal AWS partners use this to mark opportunities in GovWin and have them flow automatically through HubSpot CRM, where the BD team reviews and adds three ACE-required fields (Partner Need from AWS, Delivery Model, Customer Use Case), and into AWS Partner Central as ACE-submitted co-sell deals. The integration handles three boundaries the rest of the market makes you stitch together yourself:

  1. GovWin IQ to HubSpot. Marked opportunities sync into HubSpot hourly with their agency, contacts, and contract details pre-populated across 30 custom properties.
  2. HubSpot to AWS Partner Central. When a deal moves to a "Submit to AWS" stage, a HubSpot webhook fires, the integration calls CreateOpportunity -> AssociateOpportunity -> StartEngagementFromOpportunityTask against the AWS Partner Central Selling API, and the engagement is queued for AWS review.
  3. AWS Partner Central back to HubSpot. EventBridge events on aws.partnercentral-selling flow into a handler that updates the HubSpot deal stage. It mirrors AWS review status (Submitted to AWS, Under AWS Review, Approved by AWS, Action Required) and, when a deal reaches a terminal lifecycle stage, that wins over review status: Closed Lost mirrors to Closed Lost (with the closed-lost reason), and Launched mirrors to Closed Won.

Most of the mandatory ACE fields are auto-populated from GovWin data. Three fields require manual entry by BD on the Submit card because they reflect business judgment that cannot be derived from GovWin: Partner Need from AWS, Delivery Model, and Customer Use Case. In the AWS production catalog, AWS Solution is also required (it has a Terraform-set default for the typical case); in the Sandbox catalog it is optional.

The sync runs incrementally and respects both GovWin's 4,000 calls/hour cap and the AWS Partner Central 1 write/sec, 10 reads/sec quotas.

Pipeline Overview

How It Works

For your BD team

  1. Find an opportunity in GovWin IQ and click "Add to Web Services Download" on the opportunity detail page.
  2. The integration syncs it to HubSpot on the next scheduled run (default: every hour). A deal appears in your GovWin Pipeline with the opportunity details, agency, and contacts already filled in.
  3. Open the deal and use the "Submit to AWS Partner Central" card. In the Submit form, fill the three required ACE fields (Partner Need from AWS, Delivery Model, Customer Use Case) and confirm or change the AWS Solution (required in the AWS production catalog, optional in Sandbox; defaults to the Terraform-set ace_default_solution_id). You will also typically want to set the AWS Account ID, Industry, Deal Name, Amount, and Close Date if they were not pre-populated; the BD User Guide walks through every field on the form. Click Submit; the card writes the fields and advances the deal stage for you; BD never drags pipeline stages by hand.
  4. The submission fires automatically via HubSpot webhook. The deal moves through the AWS Partner Central review and the card's read-only status follows AWS as it responds. To close or launch a live deal, use the card's Update form (LifeCycle Stage dropdown), not a manual stage move.

For the step-by-step operator walkthrough, see the BD User Guide.

Under the hood

The GovWin to HubSpot half:

GovWin to HubSpot architecture

  • EventBridge Scheduler fires the orchestrator Lambda on a configurable cadence (default: hourly).
  • AWS Lambda (x2) owns the sync: an orchestrator that does discovery + token refresh + SQS fan-out, and a worker that drains the queue, fetches each opportunity bundle from GovWin, and pushes batches to HubSpot. Concurrency is governed by reservedConcurrentExecutions.
  • SQS carries one message per opportunity batch; partial-batch failures are reported via ReportBatchItemFailures so a stuck batch never blocks the rest.
  • DynamoDB tracks sync cursors, per-opportunity update timestamps, and GovWin-to-HubSpot ID mappings.
  • Secrets Manager stores GovWin credentials, OAuth tokens, and the HubSpot REST token.
  • SNS sends email notifications on terminal failures; an SQS dead-letter queue captures messages that exceed the retry budget.

The HubSpot to AWS Partner Central half:

HubSpot to AWS Partner Central architecture

  • HubSpot developer-platform app (private, static auth) registers webhook subscriptions for the deal properties we care about.
  • API Gateway HTTP API in front of a small Lambda receiver that validates X-HubSpot-Signature-v3 and routes events into either the submit queue (deal-stage transitions) or the update queue (content-property changes).
  • Two SQS queues with DLQs decouple webhook delivery from the AWS Partner Central API calls so we never blow HubSpot's 5-second response budget.
  • Six AWS Lambdas on the submission half: hubspot_webhook_receiver validates signatures and routes to the right queue, submit_to_ace runs the three-call submission with resume-from-step idempotency, update_in_ace handles UpdateOpportunity with optimistic locking, handle_ace_event consumes EventBridge events from aws.partnercentral-selling to mirror AWS-side state changes back into HubSpot, reconcile_pending sweeps deals whose content edits were deferred during the AWS review window, and a pair of UI Extension Lambdas (ui_extension_reads and ui_extension_writes) back the Submit and Update cards on the deal record.
  • DynamoDB ACE# pk pattern stores the AWS opportunity ID, ClientToken, engagement task ID, and last-modified date for optimistic locking on subsequent updates.

ACE-Ready Deals

The integration auto-populates most of the mandatory fields required by AWS Partner Central (ACE). The remaining fields are entered by BD on the Submit card because they reflect business judgment that cannot be derived from GovWin.

# ACE Mandatory Field HubSpot Property Source Auto-populated
1 Project Title dealname GovWin title Yes
2 Project Description description GovWin description (sanitized) Yes
3 Customer Company Name Associated Company name GovWin govEntity.title Yes
4 Industry Vertical govwin_industry NAICS code mapped to AWS industry Yes
5 Country govwin_country GovWin country Yes
6 Target Close Date closedate GovWin pAwardDateTo or responseDate Yes
7 Expected AWS Monthly Revenue amount GovWin oppValue x 1000 Yes
8 Opportunity Type govwin_ace_opportunity_type Default: "Net New Business" Yes
9 Stage dealstage Mapped from GovWin status Yes
10 Partner Need from AWS govwin_ace_partner_need BD-entered (Submit card) Manual
11 Delivery Model govwin_ace_delivery_model BD-entered (Submit card) Manual
12 Customer Use Case govwin_ace_use_case BD-entered (Submit card) Manual

The AWS Solution to associate with each opportunity is set at the project level via the ace_default_solution_id Terraform variable. In the AWS production catalog the BD user can override it per-deal on the Submit card and a Solution is required; in the Sandbox catalog the Solution is optional.

For the full end-to-end ACE submission workflow, see the ACE Integration Guide.

Before you install

Read docs/pre-install-checklist.md first. It covers the stakeholders to engage, the configuration decisions to make BEFORE terraform apply (the AWS catalog choice, the default Solution ID, the notification email, and the pipeline-stage IDs that drive ACE submission), the compliance posture, and the cost expectations. The deployment steps below assume those decisions are made.

Prerequisites

  • Deltek GovWin IQ subscription with WSAPI V3 access (Client ID, Client Secret, username, password)
  • HubSpot account (Professional or Enterprise) with a private-app token plus a separate developer-platform app for webhook delivery (created via hs project create, see Deployment Guide)
  • AWS Partner Central account in good standing, with at least one Approved Solution registered (run aws partnercentral-selling list-solutions --catalog AWS to confirm).
  • AWS account in us-east-1 (the Partner Central Selling API is region-locked). No AWS administrator access is required at any step. The project ships a terraform/bootstrap/ module that creates a least-privilege deployer role; a one-time bootstrap operator runs that module with a scoped policy (terraform/bootstrap/policies/bootstrap-operator.json) and is deleted afterwards. See SECURITY.md for the full IAM story.
  • Terraform >= 1.11 (install guide)
  • AWS CLI configured with credentials (aws configure)
  • Python >= 3.12 for building the Lambda layer
  • uv for the lockfile workflow (pip install uv or brew install uv); make package reads the committed requirements.lock, and make lock regenerates it from pyproject.toml via uv export
  • HubSpot CLI (npm install -g @hubspot/cli) for the developer-platform app
  • (Optional) Docker for local testing with LocalStack (the project pins localstack/localstack:3.8 so contributors don't need a paid LocalStack license)

Quick Start

1. Clone the repository

git clone https://github.com/pandora-cloud/govwin-hubspot-ace.git
cd govwin-hubspot-ace

2. Create a HubSpot API token

Log in to HubSpot as a Super Admin and go to Settings > Integrations > Service Keys. Create a key named "GovWin Integration" with these scopes:

  • crm.objects.deals.read / crm.objects.deals.write
  • crm.objects.companies.read / crm.objects.companies.write
  • crm.objects.contacts.read / crm.objects.contacts.write
  • crm.schemas.deals.read / crm.schemas.deals.write
  • crm.schemas.companies.read / crm.schemas.companies.write
  • crm.schemas.contacts.read / crm.schemas.contacts.write

Copy the token (starts with pat-na1- or pat-na2-). If Service Keys are not available in your HubSpot account, create a Private App instead under Settings > Integrations > Private Apps with the same scopes. See the Deployment Guide for details on both options.

3. Get GovWin API credentials

You need four values: Client ID, Client Secret, username (email), and password. Your GovWin administrator provisions API access under Admin > Web Service API in the GovWin IQ portal. The username is the email address of a GovWin user account - a dedicated API user is recommended for production. See the Deployment Guide for step-by-step instructions and security considerations.

4. Configure Terraform variables

cp terraform/terraform.tfvars.example terraform/terraform.tfvars

Edit terraform/terraform.tfvars with your credentials and preferred settings:

# Required
govwin_client_id          = "your-client-id"
govwin_client_secret      = "your-client-secret"
govwin_username           = "your-email@company.com"
govwin_password           = "your-password"
hubspot_private_app_token = "pat-na1-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

# Optional
aws_region         = "us-east-1"
sync_schedule      = "rate(4 hours)"
notification_email = "alerts@company.com"

This file is gitignored and will not be committed.

5. Build and deploy

# Package Python dependencies into a Lambda layer (cross-compiled for ARM64)
make package

# Deploy infrastructure
cd terraform
terraform init
terraform plan    # Review what will be created
terraform apply   # Deploy (type "yes" when prompted)

Terraform creates all AWS resources, stores credentials in Secrets Manager, creates the HubSpot custom properties, and schedules the first sync. The GovWin Pipeline must already exist in your HubSpot account; the Deployment Guide walks through creating it with the expected stages.

6. Mark opportunities and verify

In GovWin IQ, open any opportunity and click "Add to Web Services Download". The next scheduled sync (or a manual trigger) picks it up and creates the deal in HubSpot.

To trigger a sync immediately, invoke the orchestrator Lambda directly:

aws lambda invoke --function-name govwin-hubspot-prod-govwin-orchestrator \
  --region us-east-1 /tmp/orch.json && cat /tmp/orch.json

The orchestrator does discovery + token refresh + SQS fan-out; the worker Lambda then drains the queue. See docs/architecture.md for the full design rationale.

Verify in HubSpot under Settings > Objects > Deals > Pipelines that the GovWin Pipeline shows the synced deals.

Configuration

All configuration is managed through Terraform variables in terraform/terraform.tfvars.

Variable Default Description
govwin_client_id (required) GovWin WSAPI client ID
govwin_client_secret (required) GovWin WSAPI client secret
govwin_username (required) GovWin user email for API access
govwin_password (required) GovWin user password for API access
hubspot_private_app_token (required) HubSpot Service Key or Private App access token
hubspot_webhook_app_id (required) HubSpot developer-platform app id (numeric string from hs project upload); used to recognize the integration's own webhook events
hubspot_webhook_client_secret (required) HubSpot client secret used to validate the X-HubSpot-Signature-v3 header on inbound webhooks
aws_profile default AWS CLI profile name for authentication
aws_region us-east-1 AWS region for deployment
environment prod Environment name: prod, staging, or dev
project_name govwin-hubspot Project name prefix for resource naming
sync_schedule rate(1 hour) EventBridge schedule expression for sync frequency
ace_catalog Sandbox AWS Partner Central catalog: Sandbox (testing) or AWS (production). Production deployments must explicitly set AWS; the Sandbox default is a safety guard so a misconfigured deploy cannot write to production.
ace_default_solution_id (required for ACE) AWS Partner Central Solution ID (e.g. S-1234567). Discover via aws partnercentral-selling list-solutions --catalog AWS --region us-east-1.
ace_trigger_stages submit_to_aws,submitted_to_aws Comma-separated HubSpot deal-stage internal IDs that trigger an ACE submission. Production deployments must override this with the numeric stage IDs from the HubSpot pipeline editor (e.g. 3590200042). See docs/deployment-guide.md.
govwin_opp_types ALL Opportunity types to sync: OPP, BID, TNS, FBO, OPN, TOP, or ALL
govwin_market "" (both) Market filter: Federal, SLED, or "" for both
govwin_marked_version 2.2 Marked-for-download filter: 2.2 (Web Services), 2 (Deltek CRM), "" (disabled)
govwin_saved_search_id "" GovWin saved search ID to filter opportunities
govwin_bookmarked_only false Only sync bookmarked opportunities
initial_lookback_days 365 Days to look back on first sync
max_concurrency 2 Worker reservedConcurrentExecutions (1-5; bound by GovWin 4k/hour budget)
batch_size 10 Opportunities per SQS message (1-25)
enable_notifications true Enable SNS email notifications for sync events
notification_email "" Email address for sync notifications
log_retention_days 30 CloudWatch log retention in days
tags {} Additional tags applied to all resources

Opportunity Filtering

By default, only opportunities your BD team explicitly marks in GovWin IQ are synced. This keeps your HubSpot pipeline focused on opportunities that matter.

Mode Variable How It Works
Marked for Sync (default) govwin_marked_version = "2.2" Only syncs opps where your team clicked "Add to Web Services Download" in GovWin IQ
Saved Search govwin_saved_search_id = "12345" Syncs opps matching a saved search you configured in GovWin
Bookmarked Only govwin_bookmarked_only = true Syncs opps your team bookmarked in GovWin

These modes can be combined. For example, setting both govwin_marked_version = "2.2" and govwin_bookmarked_only = true syncs only bookmarked opps that are also marked for download.

To disable filtering entirely and sync all opportunities:

govwin_marked_version = ""

This is not recommended for production - GovWin contains hundreds of thousands of opportunities, and syncing all of them would overwhelm your HubSpot pipeline.

Data Mapping

The integration creates 53 custom deal properties, 5 company properties, and 3 contact properties in HubSpot, all under the govwin_ prefix. Deals are placed in your existing GovWin Pipeline, with GovWin statuses mapped to its stage labels. For the exhaustive list with HubSpot type, field type, group, and description for each property, see docs/reference/hubspot-properties.md (auto-generated from src/hubspot/properties.py; CI fails if it drifts).

Key field mappings

GovWin Field HubSpot Property Transform
title dealname Direct
oppValue amount Multiplied by 1,000 (GovWin stores in thousands)
description description HTML stripped, truncated to 65,536 chars
pAwardDateTo / responseDate closedate Converted to HubSpot epoch milliseconds
id (e.g., OPP12345) govwin_opp_id Deduplication key
status dealstage Mapped to a pipeline stage label (see Pipeline stages below)
govEntity.title Associated Company name Creates/updates HubSpot company
primaryNAICS govwin_industry NAICS code mapped to AWS ACE industry values
solicitationNumber govwin_solicitation_number Direct
country govwin_country USA or CAN
competitionTypes[0].title govwin_competition_type Direct
contractTypes[0].title govwin_contract_type Direct

Pipeline stages

GovWin statuses map to stage labels in your GovWin Pipeline. The labels below must exist in that pipeline; the Deployment Guide explains how to override the default mapping if your stages differ.

GovWin Status HubSpot Stage Label
Pre-RFP, Pre-Solicitation, Forecast Pre-RFP, Umbrella Program Opportunity Identified
RFP Released, RFP, Solicitation Reviewing Requirements
Proposal Submitted Preparing Response
Under Evaluation, Evaluation, Source Selection, Post-RFP Submitted
Awarded, Award, Partial Award Closed Won
Cancelled, Canceled, Closed, Lost, Deleted/Canceled, Expired/Archived Closed Lost
Declined Declined
Other, plus any unrecognized status Other

Associations

Deals are linked to their government agency (Company) and agency contacts (Contacts). Companies and contacts are deduplicated across opportunities - if three deals reference GSA, a single GSA company record is shared.

For the curated GovWin-to-HubSpot field mapping, NAICS-to-industry codes, and association logic, see the Field Mapping Reference. For the property-by-property listing (all 61) generated from the source, see docs/reference/hubspot-properties.md.

Pre-deployment Testing

Before deploying to AWS, you can validate credentials and preview the sync locally.

# Copy and fill in your credentials
cp .env.example .env

# Validate connectivity to GovWin, HubSpot, and AWS
make validate

# Preview what would sync without writing to HubSpot (fetches up to 5 opps)
make dry-run

For testing against real AWS services locally using Docker and LocalStack:

make local-up       # Start LocalStack with DynamoDB, Secrets Manager, SNS, SQS
make local-test     # Run integration tests against LocalStack
make local-down     # Stop and clean up

The integration suite (tests/integration/) exercises the DynamoDB state manager and Secrets Manager paths against the live LocalStack endpoint. It auto-skips when AWS_ENDPOINT_URL is not set, so make test and CI stay hermetic.

Project Structure

src/
  config.py                  # Configuration from environment variables
  models.py                  # Pydantic models for GovWin and HubSpot data
  govwin/
    auth.py                  # OAuth2 token acquire/refresh via Secrets Manager
    client.py                # GovWin WSAPI V3 client (all endpoints)
    rate_limiter.py          # Token bucket rate limiter (4,000/hr)
  hubspot/
    client.py                # HubSpot CRM API client (batch upsert, associations)
    properties.py            # Custom property and pipeline definitions
    rate_limiter.py          # Sliding window rate limiter (100/10s)
  sync/
    mapper.py                # GovWin-to-HubSpot field transformation, NAICS mapping
    state.py                 # DynamoDB state management (sync cursors, ID mappings)
    dedup.py                 # Change detection via updateDate comparison
    orchestrator.py          # High-level sync coordination
  lambdas/
    govwin_orchestrator.py      # EventBridge Scheduler -> discovery + token refresh + SQS fan-out
    govwin_worker.py            # SQS -> per-batch fetch + HubSpot sync
    setup_hubspot.py            # One-time property/pipeline creation
    hubspot_webhook_receiver.py # API Gateway -> validate signature -> route submit / update / audit
    submit_to_ace.py            # SQS -> three-call ACE submission with resume-from-step idempotency
    update_in_ace.py            # SQS -> UpdateOpportunity with optimistic locking
    handle_ace_event.py         # EventBridge -> mirror AWS state changes to HubSpot
    reconcile_pending.py        # Sweep deals whose content edits were deferred during AWS review, retry once review exits
    _ui_extension_common.py     # Shared signature / CORS / replay gates for the UI Extension Lambdas
    ui_extension_reads.py       # GET /solutions, GET /aws-products (tight IAM)
    ui_extension_writes.py      # POST /submit, POST /update (full write-path IAM, no Create)
  alerts.py                    # Shared SNS publisher with body redaction
terraform/
  main.tf                    # Root module wiring
  variables.tf               # All configurable inputs
  outputs.tf                 # Terraform outputs (ARNs, URLs)
  provider.tf                # AWS provider configuration
  modules/
    kms/                     # Cross-cutting customer-managed CMK consumed by ace, dynamodb, monitoring
    lambda/                  # Shared Lambda execution role + dependency layer + source archive
    govwin_sync/             # GovWin orchestrator + worker, SQS fan-out, EventBridge Scheduler
    ace/                     # ACE submission half: webhook receiver, submit/update, EventBridge handler, UI Extension reads + writes Lambdas
    dynamodb/                # DynamoDB tables (CMK-encrypted)
    secrets/                 # Secrets Manager secrets
    monitoring/              # SNS, SQS, CloudWatch alarms (incl. fan-out detector)
tests/
  unit/                      # 600+ unit tests (hermetic; lint + mypy + drift-CI gates)
  integration/               # LocalStack integration tests (skipped without AWS_ENDPOINT_URL)
  conftest.py                # Shared pytest fixtures
scripts/
  validate.py                # Pre-deployment credential validation
  dry_run.py                 # Preview sync without writing to HubSpot
  localstack-init.sh         # LocalStack resource initialization
docs/
  architecture.md            # System design and AWS resource details
  field-mapping.md           # Complete GovWin-to-HubSpot field mapping
  deployment-guide.md        # Step-by-step deployment instructions
  ace-integration.md         # ACE submission workflow
  testing-in-your-account.md # Full test pyramid + sandbox smoke + production rollout reference
  operations.md              # Alarms, stuck-deal recovery, fault-injection, DR
  diagrams/                  # Architecture and pipeline diagrams (SVG + drawio)

Documentation

Planning and install

Reference

  • Architecture Overview - System design, sync flow, DynamoDB schema, rate limiting strategy
  • Field Mapping Reference - All mapped properties, NAICS-to-industry codes, pipeline stages, associations
  • Cost model - Per-component breakdown at small / medium / large deployment sizes; cost monitoring runbook

Validate, test, operate

  • Testing in your AWS account - The full test pyramid (unit -> static -> LocalStack -> validate -> dry-run -> sandbox smoke -> production smoke), the 11-scenario smoke matrix, MFA / Sandbox-Solution gotchas, criteria for flipping to the AWS catalog
  • Operations - CloudWatch alarms, stuck-deal recovery, fault-injection, scaling + fan-out detection, disaster recovery

Architecture decisions

Security

  • All API credentials (GovWin Client ID/Secret, username/password, HubSpot token) are stored in AWS Secrets Manager and never passed as plaintext environment variables.
  • GovWin OAuth tokens are cached in Secrets Manager and refreshed automatically before expiry. The refresh flow avoids the 5-attempt lockout on the password grant.
  • Lambda execution roles follow least-privilege principles - each function can only access the specific Secrets Manager keys and DynamoDB tables it needs.
  • terraform.tfvars and .env are gitignored. Secret detection runs in the GitLab CI pipeline to catch accidental credential commits.
  • DynamoDB tables, SNS topic, and every SQS queue encrypt at rest under a single customer-managed CMK in module.kms. CloudTrail records every key use under the project's own keyId rather than the unauditable alias/aws/<service> defaults. Secrets Manager encrypts all stored values with KMS.
  • No VPC is required since the integration only calls external APIs, reducing attack surface and eliminating NAT Gateway costs.

Estimated Cost

Deployment size Opportunities synced BD ops / day Monthly cost
Small ~1,000 5-50 ~$6
Medium ~10,000 100-200 ~$20
Large ~100,000 500-1,000 ~$80

Main cost drivers: Lambda invocations, DynamoDB reads/writes, CloudWatch logs + alarms, KMS key use, Secrets Manager. EventBridge Scheduler, SNS, and SQS fall within their free tiers at small scale. Lambda runs on ARM64 (Graviton2) for a 20% cost reduction over x86. Full per-component breakdown and cost monitoring runbook in docs/cost-model.md.

Development

make install-dev    # Install development dependencies (ruff, mypy, pytest, etc.)
make test           # Run 600+ unit tests (no Docker required)
make local-up       # Start LocalStack 3.8 (community edition; no auth token needed)
make local-test     # Run 6 LocalStack integration tests
make local-down     # Tear down LocalStack
make lint           # Check code with ruff
make format         # Auto-format code with ruff
make typecheck      # Run mypy type checking
make package        # Build the ARM64 Lambda layer zip (requires uv-managed requirements.lock)
make lock           # Regenerate requirements.lock from pyproject.toml via uv
make audit          # Run pip-audit against the locked dependencies

Operational tooling:

make dlq-status                          # Depth across every project DLQ
make dlq-redrive QUEUE=<dlq-name>        # Redrive a DLQ back into its source queue
make reconcile GOVWIN_ID=OPP12345        # 4-way state dump (DDB + HubSpot + AWS) for one opportunity

Optional but recommended:

pip install pre-commit && pre-commit install   # Local pre-commit hooks (ruff, mypy, terraform fmt, gitleaks)
PYTHONPATH=. .venv/bin/python scripts/verify_fips.py   # Confirm every AWS service resolves to a FIPS endpoint

FAQ

Why two HubSpot apps (private app + developer-platform app)? HubSpot's webhook signing (X-HubSpot-Signature-v3) requires a clientSecret that only developer-platform apps issue; private apps have no client secret. Developer-platform apps cannot expose static REST tokens, so calls back into HubSpot still need the private app. Both are managed by the deployer in the same HubSpot account.

Why DynamoDB for sync state and not S3-with-git or RDS? Pay-per-request DynamoDB is the right shape for key-value sync tracking: predictable cost at our scale, sub-10ms reads, native conditional writes for idempotent ClientToken reservation, native TTL for the EventBridge dedup window. RDS would add a VPC requirement; S3 would force eventual consistency we don't want for the dedup path.

Why us-east-1 only? The partnercentral-selling API is exposed only in us-east-1. The rest of the integration (Lambdas, DynamoDB, SQS, etc.) is region-flexible via the aws_region Terraform variable, but the boto3 client for partnercentral-selling is hard-pinned to us-east-1 in src/aws_clients.py. GovCloud (us-gov-west-1) support is on the roadmap pending AWS exposing the API there.

Why Python and not TypeScript or Go? AWS Lambda's Python runtime has the smallest cold-start surface for this workload, the federal AWS partner ecosystem is overwhelmingly Python on the data-pipeline side, and the dependency surface is small enough that staying single-language keeps maintenance simple. We're not changing this.

Why Apache-2.0 and not MIT? Apache-2.0 includes an explicit patent grant from contributors. For a project that touches federal contracting workflows, that grant is meaningful. AWS's open-source amplification programs also prefer Apache-2.0 over MIT.

What's the difference between ace_catalog = "Sandbox" and "AWS"? Sandbox is AWS's safe-to-test catalog: opportunities you create are not visible to AWS reviewers and don't count against any partner reporting. The IAM policy gates this with a partnercentral:Catalog: Sandbox condition so you cannot accidentally write to production. Flip to "AWS" only after the sandbox smoke matrix is green. See docs/testing-in-your-account.md.

What does the AWS bill look like? About $6/month at ~1,000 opportunities and ~10 ACE submissions per month. Lambda invocations + DynamoDB + Secrets Manager dominate; everything else sits in free-tier territory. Lambda runs on ARM64 (Graviton2). See "Estimated Cost" above and docs/testing-in-your-account.md.

What if my organization is in GovCloud? Today this won't work end-to-end because AWS does not expose partnercentral-selling in GovCloud regions. The GovWin -> HubSpot half could run in GovCloud, but the AWS Partner Central submission half cannot until AWS adds the endpoint. We'll ship FedRAMP-Moderate-aligned operating instructions as soon as that's available; see ROADMAP.md.

Will you add a Salesforce / Pipedrive / Dynamics adapter? No. The project's identity is the GovWin -> HubSpot -> AWS Partner Central triad. Forks with adapters for other CRMs are welcome but won't be merged upstream. See CONTRIBUTING.md.

Where do I report security issues? GitHub private security advisories or email pc@pandoracloud.net (PGP key in .well-known/security/). Do not open a public issue. SLAs in SECURITY.md.

License

Apache License, Version 2.0. Copyright 2026 Pandora Cloud. See LICENSE for the full text and the patent grant terms.

About

Open-source pipeline from Deltek GovWin IQ to AWS Partner Central via HubSpot CRM. Serverless on AWS, Terraform-deployed, Apache 2.0. No paid connector dependencies.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages