CRM & Sales8 min read

How to Integrate Attio with Your Headless CMS

Connect Attio to your headless CMS so sales teams see the latest customer stories, partner pages, product launches, and account content inside the CRM as soon as it ships.

Published April 29, 2026
01Overview

What is Attio?

Attio is a CRM for go-to-market teams that lets you model companies, people, deals, lists, and workflows around your sales process. Teams use it to track accounts, route leads, run outreach, and keep customer context in one place. Its API-first data model makes it a good fit for teams that want CRM records to reflect what’s happening across their website, product, and content workflows.


02The case for integration

Why integrate Attio with a headless CMS?

Sales teams lose context when CRM data and published content live in separate systems. A customer story goes live, a partner profile gets updated, or a pricing page changes, but the account owner still has to find the link, copy details into Attio, and remember which companies match that content. That manual step breaks fast, especially when you’re publishing 10 or 50 updates a week.


03Architecture

Architecture overview

A typical Sanity and Attio integration starts with a publish event in Sanity Studio. A webhook fires for a specific document type, such as customerStory, partnerProfile, or productLaunch, and sends the document ID to a Sanity Function or webhook endpoint. That server-side handler uses @sanity/client and GROQ to fetch the published document from the Content Lake, including referenced data like company domain, industry, region, product names, and canonical URL. The handler then calls Attio’s REST API at https://api.attio.com/v2, usually against standard objects like companies or people. For account-level content, you can use Attio’s record assert endpoint for the companies object and match on the domains attribute, then write custom attributes such as latest_customer_story_url, content_industry, featured_products, or sanity_document_id. The result is visible to the end user in Attio: a rep opens an account record and sees the latest approved content tied to that company, without waiting for a nightly import.


04Use cases

Common use cases

📣

Customer story sync

When a customer story is published in Sanity, update the matching Attio company record with the story URL, industry, region, and products mentioned.

🤝

Partner directory to CRM

Publish partner profiles from Sanity and mirror partner status, website domain, region, and program tier into Attio company records.

🎯

Account-based sales context

Tag content by segment in Sanity, then write relevant assets to Attio so reps can find the right proof points for finance, healthcare, retail, or enterprise accounts.

🚀

Launch readiness tracking

Sync product launch pages, enablement links, and release dates into Attio so sales teams know which accounts should hear about a new feature.


05Implementation

Step-by-step integration

  1. 1

    Set up Attio API access

    In Attio, create or choose a workspace, then generate an API key from the developer settings. Give the key access to the objects you’ll write to, such as companies, people, or a custom object. Create custom attributes before syncing, for example sanity_document_id, latest_customer_story_url, content_industry, and featured_products.

  2. 2

    Install the Sanity client

    In your webhook handler, Sanity Function, or middleware app, install @sanity/client. Attio’s REST API works with fetch in Node 18 and later, so you don’t need a separate package unless your team prefers an HTTP client.

  3. 3

    Model the source content in Sanity Studio

    Create schemas that include the fields Attio needs. For a customerStory document, include title, slug, publishedAt, company reference, company domain, industry, region, products, and status. Keep the Attio matching field, usually the company domain, required.

  4. 4

    Create the sync trigger

    Add a Sanity webhook filtered to published document types, such as _type == 'customerStory' && !(_id in path('drafts.**')). Point it to a Sanity Function or secure API route. Include the document ID in the payload.

  5. 5

    Call Attio’s API

    In the handler, fetch the document with GROQ, map Sanity fields to Attio attribute slugs, and call Attio’s record assert endpoint for the right object. For company content, match on domains so repeat publishes update the same Attio company record instead of creating duplicates.

  6. 6

    Test the sales workflow

    Publish one test customer story, confirm the webhook fired, check the Attio company record, and verify that custom attributes are filled correctly. Then test edits, missing domains, deleted content, and permission errors before sending production events.


06Code

Code example

typescriptapp/api/sanity-to-attio/route.ts
import {createClient} from '@sanity/client'

const sanity = createClient({
  projectId: process.env.SANITY_PROJECT_ID!,
  dataset: process.env.SANITY_DATASET!,
  apiVersion: '2025-02-19',
  token: process.env.SANITY_READ_TOKEN!,
  useCdn: false,
})

export async function POST(req: Request) {
  const {documentId} = await req.json()

  const story = await sanity.fetch(`*[_id == $id][0]{
    title,
    "slug": slug.current,
    industry,
    "companyName": company->name,
    "domain": company->domain,
    "products": products[]->name
  }`, {id: documentId.replace('drafts.', '')})

  if (!story?.domain) {
    return Response.json({skipped: 'Missing company domain'}, {status: 200})
  }

  const res = await fetch('https://api.attio.com/v2/objects/companies/records', {
    method: 'PUT',
    headers: {
      authorization: `Bearer ${process.env.ATTIO_API_KEY}`,
      'content-type': 'application/json',
    },
    body: JSON.stringify({
      data: {
        matching_attribute: 'domains',
        values: {
          domains: [story.domain],
          name: story.companyName,
          latest_customer_story_title: story.title,
          latest_customer_story_url: `https://example.com/customers/${story.slug}`,
          content_industry: story.industry,
          featured_products: story.products,
          sanity_document_id: documentId,
        },
      },
    }),
  })

  if (!res.ok) {
    return Response.json({error: await res.text()}, {status: 502})
  }

  return Response.json({synced: true})
}

07Why Sanity

How Sanity + Attio works

Build your Attio integration on Sanity

Sanity gives you the structured content foundation, real-time event system, and flexible APIs to connect published content with Attio records.

Start building free →

08Comparison

CMS approaches to Attio

CapabilityTraditional CMSSanity
CRM-ready content structureContent often lives inside pages, so teams may need manual copy, exports, or HTML parsing before writing to Attio.The AI Content Operating System structures content as typed JSON in the Content Lake, with references that GROQ can join in one query.
Real-time sync on publishSales teams often wait for manual updates, scheduled exports, or nightly jobs.Webhooks trigger on publish events, and Functions can run the Attio write without separate infrastructure for common sync jobs.
Field-level control for Attio writesExports can include too much page data, too little metadata, or fields that don’t match CRM attributes.GROQ selects exact fields, such as domain, industry, products, URL, and publish date, then maps them to Attio attribute slugs.
Sales context from content referencesRelated products, regions, authors, and customer data may be embedded in page layout or plugin data.A single GROQ query can fetch a story plus referenced company, product, region, and campaign data for one Attio update.
AI agent access to the same contentAgents often need a separate index built from rendered pages or exports.Agent Context gives production AI agents read-only, scoped access to structured content that also feeds Attio and your site.

09Next steps

Keep building

Explore related integrations to complete your content stack.

Ready to try Sanity?

See how Sanity's Content Operating System powers integrations with Attio and 200+ other tools.