developers/guideGet a test team

Connecting your own CRM to Liney

The human read of the contract: what you build, the four calls, push or pull, and the first hour, in that order, then the details the connect page asks for. Ten minutes to read. The exact field names and replies are in the contract, which your coding agent reads for you.

What you build

Liney turns one sentence a rep types after a call into field changes, notes and tasks in your CRM. Your side is small, and it goes in one direction at a time (contract, section 1):

  1. You push your schema to Liney: your deal, contact and company fields, your stages, your users. One call.
  2. You push your records: the deals, contacts and companies those users work. Liney never reads your CRM directly; everything it knows is what you pushed.
  3. You take two calls from Liney: a write (a rep confirmed a line) and an undo (they took it back). Or you ask Liney for them instead of taking them. That choice is the push-or-pull question below.
  4. You push back the records a write changed, so the next line reads the current values.
  5. You sign reps in from your CRM with a link or the embedded line box, so nobody makes a Liney account.

The Node, Python and Go starters do all five, and the checker runs your integration through every case and says what to change.

The four calls

Two calls you make to Liney with your key, and two Liney makes to you, signed with your secret. The contract has the exact fields and replies; each heading links to its section.

  1. Push schema (POST /api/crm/custom/v1/schema): your deal, contact and company fields with their types and roles, your stages, your owners with their emails, and the rules your CRM enforces. The minimum is deal fields with the roles name, stage, amount, closeDate and owner, at least one open and one closed stage, and owners with emails. Send it whenever your fields, stages, owners or rules change; each push replaces what the last one said.
  2. Push records (POST /api/crm/custom/v1/records): deals, contacts and companies, each with its owner and its field values keyed by your field keys. upsert changes what you send and leaves the rest; replace makes each mentioned owner’s set exactly what you send. Push at least daily, and push the records a write or undo changed before you reply to it.
  3. Take a write (POST <your endpoint>/liney/write): the rep, field values by object type and record id, things to create, links to add, tasks to complete. Reply ok with previous for every field the call set (that is what makes undo possible) and created with your own ids. A refusal is HTTP 200 with ok: false and a code from a closed list, and it becomes a question for the rep, not an error. A line may arrive as several calls, each with its own Idempotency-Key.
  4. Take an undo (POST <your endpoint>/liney/undo), within 30 days of a line. It is compare-and-set: expect holds the value Liney last read, restore the value to put back (null clears). If any expected value differs from what the record holds now, reply conflict and change nothing. remove lists ids Liney created in that line.

Every call Liney makes to you carries a timestamp, a nonce and an HMAC-SHA256 signature made with the secret (section 7), and every item carries its own key, so a retry re-sends only what failed (section 8). Reps arrive from your CRM through a signed link or the embedded line box (section 10); a CRM whose own UI shows the line box can send lines server to server instead (section 11).

Push mode

Liney calls your server. When a rep confirms a line, Liney sends POST <your endpoint>/liney/write at once, signed, and waits up to ten seconds for your reply. An undo is POST <your endpoint>/liney/undo. Every call carries a timestamp, a nonce and an HMAC signature made with the secret; you verify it before you touch the body (contract, section 7).

Use it when your CRM is a hosted web application with a public https address, or you can put one in front of it. It is the fastest mode and the usual choice for production.

Pull mode

Your server calls Liney. A worker asks GET /api/crm/custom/v1/outbox with your key; Liney holds the request open for up to 25 seconds and answers the moment there is a call for that key’s target, with the same body a push call would carry plus an id and a lease. Your worker runs your handler and posts the reply back to /outbox/<id>. Nothing on your side listens on any port for Liney’s sake, and there is no signature to verify: the key is the authentication (contract, section 9).

Use it when your CRM cannot take inbound traffic: on-prem, behind a firewall, a desktop application, or a database with a scheduled job. Also when you are just starting, because it works from a laptop with nothing deployed.

Which should I pick?

Same writes, same undo, same request log, same checker cases; the difference is who opens the connection. If you have a public https address for your staging server today, push: it is the default on the connect page and the usual choice. If you don’t, pull, and switch the target to push later if you want; the same handler code serves both. A line is a few seconds slower in pull mode because it waits for your next poll; reps don’t notice in practice.

The first hour

The bar the kit is built to: from sign-up to a working line in under an hour, on one page. Timed end to end on the final build: 15 min 52 s from the sign-in link to live.

  1. A test team, at once. Sign up at /developers and get a team with a sample CRM in it: fake deals, fake reps, a test key, an address. No approval, no call, no billing.
  2. Copy the prompt into Claude Code or Codex. The key and the address are already in it; the secret is not (it lives in LINEY_SECRET, shown once on the card).
  3. Push schema and records (or start with the seeded sample and push yours when you have them) and type a line in the playground against your own fields. Suggestion cards appear in the rules editor from that first push: Liney reads your fields and already understands your process, before any endpoint exists.
  4. liney dev forwards Liney’s write and undo calls to your laptop, so you test against localhost with no deploy and no tunnel (section 12).
  5. The playground and the request log. Type a line as a fake rep and watch the question Liney asks, the exact call sent to your endpoint, your reply, then an undo. Every call and reply for the last seven days, with a replay.
  6. npx liney-check until green (section 13): with the test key it runs every case against the test target and prints one line per case with what to change and the section it is about; --case <id> reruns one case in seconds. In pull mode add --pull and have your poller (or liney dev) running.
  7. Go live (section 14), three steps on the connect page: save the live target, run the live subset against it with the live key (it changes nothing), press Go live. From then on writes go to the live target and the test key keeps its own. The connect page then shows a health line: the last write that landed, the last failure with a request-log reference, your endpoint’s p95. The GitHub Action runs the checker on every push and gives you a badge.
LINEY_URL=https://www.liney.ai LINEY_KEY=lk_test_… npx liney-check https://staging.example.com

Any language, any framework

There is nothing to install and no SDK. The whole integration is HTTPS and JSON: a few calls you make to Liney with a bearer key, two routes you serve (or one polling loop), and an HMAC-SHA256 signature check that every language has in its standard library. PHP, Ruby, Java, .NET, Next.js, Django, Rails, Laravel, a Go binary, a Python script beside a database: all the same to Liney.

  • Starters exist in Node, Python and Go. Each is one file that does everything in this guide and passes the checker; in another language, hand your agent the closest one and the contract and it will translate.
  • Push mode needs your framework to serve two POST routes and read the raw request body (before any JSON middleware parses it) for the signature. Every framework can; the contract says how.
  • Pull mode needs one long-running worker (a process, a container, a systemd unit, a background job) that can keep an HTTP request open for 25 seconds. A plain cron job that runs every minute also works, with a line a minute slower.
  • The environment variables below are just configuration. Read them however your stack reads config: process.env, os.environ, getenv, Rails credentials, a Laravel .env, a Kubernetes secret. The names are only what the starters and the checker use; your own code can call them anything, as long as the secret never lands in a file you commit.

Keys, the secret and the two targets

Making the connection on the connect page gives you three credentials, shown once:

  • The test key (lk_test_…). Your developers build and test with it. It pushes schema and records and it drives the checker.
  • The live key (lk_live_…). What your production server uses once the team goes live. Until then it can only run the checker’s non-mutating subset.
  • The secret (ls_…). Signs every call Liney makes to you, and the sign-in tokens you mint for reps. It lives in LINEY_SECRET on your server and nowhere else: not in the prompt, not in a file, not in a log.

Each key has a target: where Liney sends a rep’s line when it was written with that key’s data. The test target is where writes go while you build. The live target is your real CRM, used once an admin presses Go live. Each target has its own delivery mode, and you can change either at any time before go-live (after it, the live target only moves to one the checker has passed against).

What to put in “Endpoint URL”

Push mode asks for one address: the base URL of your server, https only. Liney adds /liney/write and /liney/undo to it. A path is kept, so https://crm.example.com/hooks means Liney calls https://crm.example.com/hooks/liney/write.

It does not have to exist yet. Liney sends nothing to it until a rep confirms a line or the checker runs, so put in the address your server will have (your staging host for the test target, production for the live target) and build towards it. Change it from the connect page whenever you like before go-live; the checker also sets the test target when you pass it a URL.

Two rules: a test target may not point at any live target’s URL (Liney refuses the collision, so a test run can never touch real data), and the live target must be set before Go live. No public address yet? Leave the test target in pull mode and read the next card.

Building on a laptop

Three ways to run against a server on localhost, with nothing deployed and no tunnel:

  1. liney dev. Keep your server in push mode, exactly as it will run in production, and run liney dev http://localhost:3001 next to it (the prompt on the developer screen has the full command with your key). It is pull mode with the poller on your laptop: it long-polls the outbox with your test key, POSTs each call to your local /liney/write or /liney/undo signed with the secret exactly as Liney would, and posts your reply back. Your signature check, your handlers and your pushes run unchanged. The test target must be in pull mode for this.
  2. Your own poller, if you are going to run pull mode in production anyway. The starters include one behind a flag.
  3. A tunnel (ngrok and the like) with the test target in push mode pointing at it. Works, but the first two need no third party.

The environment your server needs

  • LINEY_URL: Liney’s address, https://www.liney.ai (a preview deployment gives you its own).
  • LINEY_KEY: the test key while building, the live key in production.
  • LINEY_SECRET: the secret. Read it from the environment and nowhere else.
  • LINEY_TEAM: your team’s slug (the acme in liney.ai/acme), used by the sign-in link and the embedded box.

The keys card on the connect page copies all of these as a .env snippet, once, right after the connection is made.

Where everything is

  • Get a test team: an email gets you a team with a seeded sample CRM (Northwind) to build against, and the console this guide describes.
  • The contract: every call, field and reply, with anchors. What your agent reads. Also as OpenAPI.
  • The starters and the GitHub Action are in the rail. The developer screen in your team’s console has the checklist, the playground (dry runs against your server) and the request log with every call and reply.
  • Stuck? hello@liney.ai, with the request-log reference if there is one.