# Journeys API: integration guide (with code)

**Category:** [CRM](https://help.totalctrl.app/hc/totalctrl/totalctrl-help-center/en-US/categories/crm-1)
**Updated:** 2026-08-19

## Integrate your product with Journeys

Journeys run on contact data your application supplies. You integrate by sending two events to the **contacts ingest API**: one when a user signs up, and one each time they log in. Everything else (enrollment, scheduling, sending) happens automatically.
### 1. Get an API token

In TotalCtrl go to **Settings → API Tokens** and create a token. Send it as a bearer token on every request. Treat it like a password — keep it on your server, never in client-side code.
### 2. The endpoint

POST https://app.totalctrl.app/api/v1/crm/contacts/ingest
Authorization: Bearer YOUR_API_TOKEN
Content-Type: application/jsonFieldNotes`external_uid`Your stable user/account id. Preferred match key.`email`Used as the match key if no `external_uid`. One of the two is required.`first_name`, `last_name`, `company_name`, `plan`Optional profile fields.`signup_at`, `last_seen_at`ISO-8601 (`2026-06-17T08:30:00Z`) or epoch seconds/ms.`traits`Free-form JSON object of any extra attributes.

A brand-new contact returns `201` and fires the *contact.created* trigger (enrolling welcome journeys). An existing one returns `200`. The call is idempotent — sending it repeatedly just updates the same contact, so it is safe to call on every login.
### 3. On signup

#### cURL

curl -X POST https://app.totalctrl.app/api/v1/crm/contacts/ingest \
  -H "Authorization: Bearer $TOTALCTRL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "external_uid": "user_123",
        "email": "sam@example.com",
        "first_name": "Sam",
        "plan": "free",
        "signup_at": "2026-06-18T12:00:00Z"
      }'
#### Python

import requests, datetime

def on_signup(user):
    requests.post(
        "https://app.totalctrl.app/api/v1/crm/contacts/ingest",
        headers={"Authorization": f"Bearer {TOTALCTRL_TOKEN}"},
        json={
            "external_uid": user.id,
            "email": user.email,
            "first_name": user.first_name,
            "plan": user.plan,
            "signup_at": user.created_at.isoformat(),
            "last_seen_at": datetime.datetime.utcnow().isoformat() + "Z",
        },
        timeout=5,
    )
#### Node.js

async function onSignup(user) {
  await fetch("https://app.totalctrl.app/api/v1/crm/contacts/ingest", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.TOTALCTRL_TOKEN}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      external_uid: user.id,
      email: user.email,
      first_name: user.firstName,
      plan: user.plan,
      signup_at: user.createdAt,
    }),
  });
}
### 4. On login (keeps win-back accurate)

Refresh `last_seen_at` so inactivity segments are correct. Send only what changed:

curl -X POST https://app.totalctrl.app/api/v1/crm/contacts/ingest \
  -H "Authorization: Bearer $TOTALCTRL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "external_uid": "user_123", "last_seen_at": "2026-06-18T12:00:00Z" }'

Doing this on every login (or batched hourly) keeps a *Last seen · more than 30 days ago* win-back journey firing at exactly the right time.
### 5. Build the journey

In **CRM → Journeys**, create a journey, choose *Event — on signup* (welcome/drip) or *Segment* (win-back), add your email steps with delays, and click **Activate**. That's it — matching contacts are enrolled and emails go out on schedule.
### Notes

- Unsubscribed and hard-bounced contacts are skipped automatically.
- Each step is sent at most once per enrollment.
- Subscribe to the `crm.journey.enrolled`, `crm.journey.email_sent`, and `crm.journey.completed` webhooks to react to journey activity in your own systems.

---

## Related Articles

- [What are Journeys?](https://help.totalctrl.app/en-US/articles/journeys-overview)
- [CRM Overview](https://help.totalctrl.app/en-US/articles/crm-overview)
- [CRM overview](https://help.totalctrl.app/en-US/articles/crm-overview-1)
- [Work pipelines and deals](https://help.totalctrl.app/en-US/articles/crm-pipelines-and-deals)
- [Running Email Campaigns](https://help.totalctrl.app/en-US/articles/running-email-campaigns)

---
[← Back to TotalCtrl Help Center](https://help.totalctrl.app/en-US/)