Keep CRM up to date

Your users in your CRM

  1. onUser created on WorkOS
  2. attioLookup if user exists on Attio
  3. attioUpsert the user into Attio
  4. customFilter users or add enrichment
  5. logIf the user was created or updated

durable — pauses for approvals, survives restarts

Build it with your coding agent

Paste into any coding agent with the Terse skills installed. No skills yet? Install them in one command.

/terse-create build https://www.useterse.ai/use-cases/workos-signups-to-attio.md

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"];