Developers
Contents
On this page

Getting started

Quickstart

From nothing to a working integration in about ten minutes.

Before you start#

  • A Line Me Up business on Basic or a plan above it, and its owner's sign-in — only the owner can make keys and endpoints.
  • A terminal with curl, or Node 18, Python 3 or PHP 8 — every sample comes in all four; pick one and the whole site follows.
  • For step 4, a way to give your laptop an HTTPS address for a few minutes: any tunnelling tool will do. Webhooks are only ever sent to https:// addresses.

Try this on a quiet line, or before opening: the visit you add in step 3 is real and appears on the business's board.

1. Create a key#

  1. Sign in as the owner and open Settings → Integrations.
  2. Under API keys, choose Create key.
  3. Name it Quickstart, and tick read and visits:write. Leave lines and desks on All.
  4. Create it, and copy the key. It is shown once. Line Me Up keeps only a hash of it, so nobody — us included — can show it to you again.
# Your terminal. The key is shown once — paste it here, not into code.
export LINEMEUP_API_KEY="lmu_live_…"

2. Make your first call#

Ask who the key belongs to:

GET /v1/business

curl

curl "https://business.linemeup.in/api/v1/business" \
  -H "Authorization: Bearer $LINEMEUP_API_KEY"

Node

const res = await fetch("https://business.linemeup.in/api/v1/business", {
  headers: {
    Authorization: `Bearer ${process.env.LINEMEUP_API_KEY}`,
  },
});
if (!res.ok) throw new Error(`Line Me Up answered ${res.status}`);
const answer = await res.json();

Python

import os
import requests

res = requests.get(
    "https://business.linemeup.in/api/v1/business",
    headers={
        "Authorization": f"Bearer {os.environ['LINEMEUP_API_KEY']}",
    },
    timeout=10,
)
res.raise_for_status()
answer = res.json()

PHP

<?php
$ch = curl_init("https://business.linemeup.in/api/v1/business");
curl_setopt_array($ch, [
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer " . getenv("LINEMEUP_API_KEY"),
    ],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 10,
]);
$answer = json_decode(curl_exec($ch), true);
Response (200)
{
  "id": "bus-0193f2a1-7c4e-7a10-9b2e-000000000001",
  "name": "Sunrise Clinic",
  "slug": "sunrise-clinic",
  "category": "healthcare",
  "timeZone": "Asia/Kolkata",
  "today": "2026-10-30",
  "clockFormat": "12h"
}

A 401 means the key was not sent whole, or has a space or a line break in it. Then list the lines, and note the id of the one you will use:

GET /v1/lines

curl

curl "https://business.linemeup.in/api/v1/lines" \
  -H "Authorization: Bearer $LINEMEUP_API_KEY"

Node

const res = await fetch("https://business.linemeup.in/api/v1/lines", {
  headers: {
    Authorization: `Bearer ${process.env.LINEMEUP_API_KEY}`,
  },
});
if (!res.ok) throw new Error(`Line Me Up answered ${res.status}`);
const answer = await res.json();

Python

import os
import requests

res = requests.get(
    "https://business.linemeup.in/api/v1/lines",
    headers={
        "Authorization": f"Bearer {os.environ['LINEMEUP_API_KEY']}",
    },
    timeout=10,
)
res.raise_for_status()
answer = res.json()

PHP

<?php
$ch = curl_init("https://business.linemeup.in/api/v1/lines");
curl_setopt_array($ch, [
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer " . getenv("LINEMEUP_API_KEY"),
    ],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 10,
]);
$answer = json_decode(curl_exec($ch), true);
Response (200)
{
  "data": [
    {
      "id": "line-0193f2a1-7c4e-7a10-9b2e-000000000001",
      "name": "General",
      "code": "GEN",
      "type": "walk_in",
      "paused": false,
      "waiting": 4,
      "avgServiceMinutes": 10,
      "maxCapacity": 60,
      "description": "Walk-ins for a first visit",
      "location": "Ground floor",
      "callGraceMinutes": 5,
      "appointmentGraceBeforeMinutes": 10,
      "maxLateArrivalMinutes": 15,
      "autoNoShowEnabled": true
    },
    {
      "id": "line-0193f2a1-7c4e-7a10-9b2e-000000000002",
      "name": "X-ray",
      "code": "XR",
      "type": "scheduled_walk_in",
      "paused": false,
      "waiting": 1,
      "avgServiceMinutes": 15,
      "maxCapacity": 30,
      "description": null,
      "location": "First floor, room 4",
      "callGraceMinutes": 5,
      "appointmentGraceBeforeMinutes": 10,
      "maxLateArrivalMinutes": 10,
      "autoNoShowEnabled": true
    }
  ]
}

3. Add a visit#

Put somebody in the line, with a name and a phone number. Use your own line's id. The Idempotency-Key makes it safe to run twice: the second time returns the same visit rather than adding another — see Idempotency.

POST /v1/visits

curl

curl -X POST "https://business.linemeup.in/api/v1/visits" \
  -H "Authorization: Bearer $LINEMEUP_API_KEY" \
  -H "Idempotency-Key: crm-visit-1042" \
  -H "Content-Type: application/json" \
  -d '{"lineId":"line-0193f2a1-7c4e-7a10-9b2e-000000000001","customer":{"name":"Asha Rao","phone":"+919000000001"},"note":"First visit","reference":"CRM-1042","consent":true}'

Node

const res = await fetch("https://business.linemeup.in/api/v1/visits", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.LINEMEUP_API_KEY}`,
    "Idempotency-Key": "crm-visit-1042",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "lineId": "line-0193f2a1-7c4e-7a10-9b2e-000000000001",
    "customer": {
      "name": "Asha Rao",
      "phone": "+919000000001"
    },
    "note": "First visit",
    "reference": "CRM-1042",
    "consent": true
  }),
});
if (!res.ok) throw new Error(`Line Me Up answered ${res.status}`);
const answer = await res.json();

Python

import os
import requests

res = requests.post(
    "https://business.linemeup.in/api/v1/visits",
    headers={
        "Authorization": f"Bearer {os.environ['LINEMEUP_API_KEY']}",
        "Idempotency-Key": "crm-visit-1042",
    },
    json={
        "lineId": "line-0193f2a1-7c4e-7a10-9b2e-000000000001",
        "customer": {
            "name": "Asha Rao",
            "phone": "+919000000001",
        },
        "note": "First visit",
        "reference": "CRM-1042",
        "consent": True,
    },
    timeout=10,
)
res.raise_for_status()
answer = res.json()

PHP

<?php
$ch = curl_init("https://business.linemeup.in/api/v1/visits");
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => "POST",
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer " . getenv("LINEMEUP_API_KEY"),
        "Idempotency-Key: crm-visit-1042",
        "Content-Type: application/json",
    ],
    CURLOPT_POSTFIELDS => json_encode([
        "lineId" => 'line-0193f2a1-7c4e-7a10-9b2e-000000000001',
        "customer" => [
            "name" => 'Asha Rao',
            "phone" => '+919000000001',
        ],
        "note" => 'First visit',
        "reference" => 'CRM-1042',
        "consent" => true,
    ]),
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 10,
]);
$answer = json_decode(curl_exec($ch), true);
Response (200)
{
  "data": [
    {
      "id": "job-0193f2a1-7c4e-7a10-9b2e-000000000001",
      "journeyId": "jny-0193f2a1-7c4e-7a10-9b2e-000000000001",
      "token": "GEN-20261030-014",
      "lineId": "line-0193f2a1-7c4e-7a10-9b2e-000000000001",
      "status": "queued",
      "bookingType": "walk_in",
      "priority": false,
      "note": "First visit",
      "customer": {
        "name": "Asha Rao",
        "phone": "+919000000001"
      },
      "services": [
        {
          "id": "svc-0193f2a1-7c4e-7a10-9b2e-000000000002",
          "name": "General consultation"
        }
      ],
      "deskId": null,
      "position": 3,
      "slotAt": null,
      "createdAt": "2026-10-30T04:12:00.000Z",
      "calledAt": null,
      "startedAt": null,
      "endedAt": null,
      "updatedAt": "2026-10-30T04:12:00.000Z"
    }
  ]
}

The answer is a list because a visit for services on two lines is two visits, one on each. Open the board in the staff app: the token is in the line, at the place position says. To take it out again, cancel it — Visits → Cancel.

4. Receive a webhook#

Now the other direction. Start a small receiver that checks each delivery's signature and prints the event:

mkdir line-me-up-hooks && cd line-me-up-hooks
npm init -y && npm install express
# save the two files below as verify-line-me-up.mjs and server.mjs, then:
export LINEMEUP_WEBHOOK_SECRET="whsec_…"
node server.mjs
verify-line-me-up.mjs
// verify-line-me-up.mjs — Node 18 or later, no dependencies.
import { createHmac, timingSafeEqual } from "node:crypto";

/**
 * Whether a delivery really came from Line Me Up.
 *
 * rawBody: the request body exactly as it arrived (a Buffer or a string) —
 *          never JSON you parsed and turned back into text.
 * header:  the LineMeUp-Signature header.
 * secret:  your endpoint's signing secret, whsec_…
 */
export function verifyLineMeUp(
  rawBody,
  header,
  secret,
  { toleranceSeconds = 300, nowSeconds = Math.floor(Date.now() / 1000) } = {},
) {
  if (typeof header !== "string" || typeof secret !== "string") return false;
  const parts = header.split(",").map((part) => part.trim());
  const t = parts.find((part) => part.startsWith("t="))?.slice(2);
  if (t === undefined || !/^\d+$/.test(t)) return false;
  if (Math.abs(nowSeconds - Number(t)) > toleranceSeconds) return false;

  const expected = createHmac("sha256", secret).update(`${t}.`).update(rawBody).digest();
  return parts
    .filter((part) => part.startsWith("v1="))
    .some((part) => {
      const given = Buffer.from(part.slice(3), "hex");
      return given.length === expected.length && timingSafeEqual(given, expected);
    });
}
server.mjs
// server.mjs — npm install express
import express from "express";
import { verifyLineMeUp } from "./verify-line-me-up.mjs";

const app = express();

// express.raw, not express.json: the signature is over the bytes as sent.
app.post("/webhooks/line-me-up", express.raw({ type: "application/json" }), (req, res) => {
  const ok = verifyLineMeUp(
    req.body,
    req.get("LineMeUp-Signature"),
    process.env.LINEMEUP_WEBHOOK_SECRET,
  );
  if (!ok) return res.status(400).send("Bad signature");

  const event = JSON.parse(req.body.toString("utf8"));
  res.sendStatus(200); // answer within 10 seconds, then do the work
  console.log(event.type, JSON.stringify(event.data, null, 2));
});

app.listen(3000);
  1. Point your tunnel at port 3000 and copy its https:// address.
  2. In Settings → Integrations, under Webhooks, choose Add endpoint. Enter that address followed by /webhooks/line-me-up, and tick visit.created.
  3. Copy the signing secret — also shown once — into LINEMEUP_WEBHOOK_SECRET and restart the receiver.
  4. Press Send test event. Your terminal prints webhook.test, and the endpoint's delivery log shows the attempt, with your receiver's 200.
  5. Run step 3 again with a new Idempotency-Key. Within seconds your terminal prints visit.created:
What your receiver gets
{
  "id": "evt-0193f2a1-7c4e-7a10-9b2e-000000000001",
  "type": "visit.created",
  "apiVersion": "v1",
  "createdAt": "2026-10-30T04:12:00.000Z",
  "businessId": "bus-0193f2a1-7c4e-7a10-9b2e-000000000001",
  "test": false,
  "data": {
    "visit": {
      "id": "job-0193f2a1-7c4e-7a10-9b2e-000000000001",
      "journeyId": "jny-0193f2a1-7c4e-7a10-9b2e-000000000001",
      "token": "GEN-20261030-014",
      "lineId": "line-0193f2a1-7c4e-7a10-9b2e-000000000001",
      "status": "queued",
      "bookingType": "walk_in",
      "priority": false,
      "customer": {
        "name": "Asha Rao",
        "phone": "+919000000001"
      },
      "services": [
        {
          "id": "svc-0193f2a1-7c4e-7a10-9b2e-000000000002",
          "name": "General consultation"
        }
      ],
      "deskId": null,
      "position": 3,
      "slotAt": null,
      "createdAt": "2026-10-30T04:12:00.000Z",
      "calledAt": null,
      "startedAt": null,
      "endedAt": null,
      "updatedAt": "2026-10-30T04:12:00.000Z"
    }
  }
}

If nothing arrives, open the delivery log: each attempt shows the status your receiver answered, or why we could not reach it. A 400 Bad signature nearly always means the secret was pasted with a space, or the receiver parsed the JSON before checking it.

What next#

  • Revoke the Quickstart key and make one for real, with only the scopes it needs — Scopes and restrictions.
  • Move a visit along: call it, serve it, complete it — Visits.
  • Read the event catalogue, and how retries work.
  • Pick a guide close to what you are building.