Integrations Guide

How to connect Salesforce, HubSpot, and Stripe — including multiple instances of the same provider — run syncs, view events, and troubleshoot.

Read-only by design. Anchord ingests data from connected systems but never writes back. Guard/write endpoints are evaluation-only decision gates.

HubSpot

Connecting via OAuth

  1. Log in to the Anchord Console
  2. Navigate to Integrations
  3. Click Connect next to HubSpot
  4. Authorize in HubSpot
  5. The callback creates the integration and stores tokens

Running a sync

Navigate to Integrations → HubSpot and click Sync now. The sync pulls companies and contacts updated since the last cursor, ingests them via the batch API, and records a SyncRun.

Or via the API:

curl -s -X POST https://api.anchord.ai/app/integrations/hubspot/sync \
  -H "Authorization: Bearer <your-key>"

What the sync does

1. Resolve cursor (last sync timestamp, default: 24h ago)

2. Refresh access token if expired

3. Pull companies updated since cursor (paginated, 100/page)

4. Pull contacts updated since cursor (paginated, 100/page)

5. Map each to ingest records (system=hubspot)

6. POST /api/v1/ingest/batch in chunks of 100

7. Record SyncRun with fetched/ingested/failed counts

8. Update cursor to max(lastmodifieddate)

Disconnecting

Navigate to Integrations → HubSpot and click Disconnect. This wipes all stored secrets and marks the integration as disconnected.

Salesforce

Connecting via OAuth

  1. Log in to the Anchord Console
  2. Navigate to Integrations → Salesforce
  3. Choose your environment: Production, Sandbox, or Custom domain
  4. Click Connect Salesforce
  5. Authorize in Salesforce (read-only scopes: api, refresh_token, id)
  6. The callback stores tokens and detects Person Accounts automatically

Sync model

Navigate to Integrations → Salesforce and click Sync now. Two jobs are queued — one for Accounts, one for Contacts — and run asynchronously.

Or via the API:

curl -s -X POST https://api.anchord.ai/app/integrations/salesforce/sync \
  -H "Authorization: Bearer <your-key>"

1. Resolve per-object cursor (or backfill window: 30/90/365 days / all)

2. Refresh access token if expired (401 retry)

3. SOQL query Accounts/Contacts by SystemModstamp (paginated)

4. Map to ingest records (system=salesforce, object_type=account|contact)

5. POST /api/v1/ingest/batch in chunks of 100

6. Record SyncRun with fetched/ingested/failed counts

7. Nightly sync at 02:30 UTC for all connected integrations

Person Accounts

If your Salesforce org has Person Accounts enabled, Anchord detects this automatically on connect. Person Account records are ingested as object_type=person_account with person-level fields (name, email, phone, mailing address). Business accounts remain object_type=account.

Push-based ingest (alternative)

You can also push Salesforce data directly via the ingest API — useful for agent-driven workflows or custom ETL pipelines.

curl -s -X POST https://api.anchord.ai/api/v1/ingest/batch \
  -H "Authorization: Bearer <your-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "records": [
      {
        "system": "salesforce",
        "object_type": "account",
        "object_id": "001A000001ABC",
        "payload": {"name": "Acme Corp", "domain": "acme.com"}
      },
      {
        "system": "salesforce",
        "object_type": "contact",
        "object_id": "003B000002DEF",
        "payload": {"first_name": "Jane", "last_name": "Smith", "email": "jane@acme.com"}
      }
    ]
  }'

Disconnecting

Navigate to Integrations → Salesforce and click Disconnect. Data is preserved by default. Optionally check "Also purge Salesforce data" and type PURGE SALESFORCE to remove all SF source records and entity links.

Stripe

Webhook processing

Stripe events are received via POST /webhooks/stripe. The signature is verified (HMAC-SHA256), the event is persisted idempotently, and a processing job is dispatched.

Stripe event prefixObject type
customer.*customer
subscription.*subscription
invoice.*invoice

Idempotency

If the same Stripe event is delivered twice, the second request returns {"status": "already_received"} with HTTP 200. No duplicate rows are created.

Custom Sources

Beyond HubSpot and Stripe, you can ingest data from any system by registering a custom source.

# Register a custom source
curl -s -X POST https://api.anchord.ai/api/v1/sources \
  -H "Authorization: Bearer <your-key>" \
  -H "Content-Type: application/json" \
  -d '{"key": "my_crm", "display_name": "My CRM"}'

# Ingest with the custom source
curl -s -X POST https://api.anchord.ai/api/v1/ingest/batch \
  -H "Authorization: Bearer <your-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "records": [{
      "system": "my_crm",
      "object_type": "company",
      "object_id": "c-123",
      "payload": {"name": "Globex", "domain": "globex.com"}
    }]
  }'

You can also manage sources from the Sources page in the console. Built-in keys (hubspot, stripe) are reserved.

Viewing sync runs

Navigate to Sync Runs in the console to see a paginated table of all sync runs across providers: run ID, provider, type (manual/nightly), status, fetched/ingested/failed counts, started time, and duration. Click into any run for full details.

Troubleshooting

Every API response includes a request_id. Use it to correlate logs across sync runs, webhook events, and API errors.

SymptomLikely causeFix
Sync status = error, 401Token expiredDisconnect and reconnect
Webhook events stuckQueue worker not runningStart queue worker
Webhook returns 401Signing secret mismatchRe-register webhook
0 records fetchedCursor ahead of dataReset sync cursor
Ingest validation errorsMissing required fieldsCheck system, object_type, object_id, payload