---
title: Keep CRM up to date
description: When a new user onboards, automatically keep your CRM up to date
slug: workos-signups-to-attio
group: gtm
trigger: User created on WorkOS
steps:
  - "attio: Lookup if user exists on Attio"
  - "attio: Upsert the user into Attio"
  - "custom: Filter users or add enrichment"
  - "log: If the user was created or updated"
integrations:
  - workos
  - attio
outcome: Your users in your CRM
publishedAt: "2026-07-16"
updatedAt: "2026-07-16"
durable: true
---

Keeping your users in sync between your application and CRM is always a challenge. Outside of a simple sync, you may want to consider:

- Filtering to keep certain users out of your CRM
- Custom enrichment logic so they land in the CRM with the right data
- Custom alerting to Slack

The template below shows how to sync your users from WorkOS to Attio and outlines where you can add custom logic.

## Prerequisites

1. A Terse project (`terse init `)
2. Connected WorkOS and Attio integrations: `terse integrate` and follow steps to connect each one.

## The job

```typescript
import { createJob, log } from "terse-sdk";
import {
  AttioObject,
  toolbox,
  Triggers,
  type WorkOSUserCreatedTriggerPayload,
} from "../terse.generated";

createJob({
  name: "Create Attio user from WorkOS user",
  triggers: [Triggers.workOS.onUserCreated()],
  durable: true,
  onTrigger: async (event) => {
    switch (event.eventType) {
      case "user.created":
        await handleUserCreated(event.user);
        break;
      default:
        throw event.eventType satisfies never;
    }
  },
});

async function handleUserCreated(user: WorkOSUser): Promise<void> {
  // Option 1 - Custom filtering here: Add custom logic here to prevent user from being copied into Attio
  // if desired

  // Option 2 - Add user enrichment here

  const fullName =
    [user.firstName, user.lastName].filter(Boolean).join(" ") || undefined;

  const existing = await toolbox.attio.queryRecords({
    object: AttioObject.User,
    filter: { user_id: user.id },
    limit: 1,
  });

  // Durable since flag is enabled, will retry on API failures
  await toolbox.attio.upsertRecord({
    object: AttioObject.User,
    matchingAttribute: "user_id",
    records: [
      {
        user_id: user.id,
        primary_email_address: user.email,
        first_name: user.firstName,
        last_name: user.lastName,
        full_name: fullName,
      },
    ],
  });

  // Logging can in addition post to Slack channel below
  await log(
    existing.count > 0
      ? `Updated existing Attio user for ${user.email} (WorkOS ${user.id})`
      : `Created Attio user for ${user.email} (WorkOS ${user.id})`,
  );
}

type WorkOSUser = WorkOSUserCreatedTriggerPayload["user"];
```
