Skip to content

İşlemde is in development. Sign-ups open soon.

For developers

İşlemde API

Connect your jobs and customers to your own software, accounting or e-commerce system. Read data, open jobs and customers, change statuses, record payments, and get every change instantly by webhook.

Base URL
/api/v1
Auth
x-api-key
Rate limit
600 per minute
Plan
Business

Getting started

  1. Create a key under Settings → API in the dashboard. The key is shown only once, so store it somewhere safe.
  2. Put the key in the ISLEMDE_API_KEY environment variable on your server.
  3. Send it with every request in the x-api-key header. Your first request, jobs ready for pickup:
curl "https://islemde.app/api/v1/jobs?status=ready" \
  -H "x-api-key: $ISLEMDE_API_KEY"

Pick your language once; every example on the page switches to it.

Authentication

Send the key in the x-api-key header or as Authorization: Bearer isl_…. Keys start with isl_ and can access all of the business's jobs and customers.

Never keep the key in a browser, a mobile app or a public repository; always call the API from your own server. If you think it has leaked, revoke it under Settings → API and it stops working immediately.

Read and write

You choose a key's access when you create it. A read-only key (the default) reads lists and records and can't change anything. A read and write key can also open and edit jobs and customers, change statuses, add notes and record payments. Trying to write with a key that lacks write access returns 403 insufficient_scope. Access can't be changed later; create a new key if you need to.

Writes go through the same rules as the dashboard: the same validation, job allowance, SMS and emails to customers, job history and webhooks. The history shows the key's name as the author, e.g. API · Accounting. While the account is read-only (a payment failed or the trial ended), writes are off and reads keep working.

Every job opened with a write key counts toward your job allowance, and the customer may get an SMS. Don't test with real customer phone numbers.

Ready-made client

The examples below use this small helper: one function sends a single request and stops with a clear message on errors, the other fetches every page of a list in turn. Add it to your project once. JavaScript and C# need no extra packages; Python needs requests and PHP needs Guzzle.

// islemde.js · Node 18 ve üzeri, ek paket gerekmez
const BASE = "https://islemde.app/api/v1";

export async function islemde(path, params = {}) {
  const url = new URL(BASE + path);
  for (const [name, value] of Object.entries(params)) {
    if (value != null) url.searchParams.set(name, value);
  }
  const res = await fetch(url, {
    headers: { "x-api-key": process.env.ISLEMDE_API_KEY },
  });
  const body = await res.json();
  if (!res.ok) throw new Error(`${body.error.code}: ${body.error.message}`);
  return body;
}

// Listenin bütün sayfalarını sırayla getirir:
// for await (const job of all("/jobs")) { … }
export async function* all(path, params = {}) {
  let cursor;
  while (cursor !== null) {
    const page = await islemde(path, { ...params, cursor });
    yield* page.data;
    cursor = page.nextCursor;
  }
}

Pagination

List endpoints return up to limit records (25 by default, 100 at most) and a nextCursor. Send it back as cursor for the next page; on the last page it's null. Pages don't shift if new jobs are opened while you read, so you never get the same record twice.

With the ready-made client you don't write the loop yourself; records arrive one by one and pages are fetched in the background:

# İlk sayfa
curl "https://islemde.app/api/v1/jobs?state=all&limit=100" \
  -H "x-api-key: $ISLEMDE_API_KEY"

# Sonraki sayfa: cevaptaki nextCursor ile
curl "https://islemde.app/api/v1/jobs?state=all&limit=100&cursor=1046" \
  -H "x-api-key: $ISLEMDE_API_KEY"

Safe retries

When the network drops, you can't tell whether your request reached us. Add a unique Idempotency-Key header to each new POST request (a UUID, say) and send the same value when you retry. Within 24 hours, a request with the same key and body won't create a second record; you get the first successful response back with an idempotent-replayed: true header.

  • Sending the same key with a different body returns 422 idempotency_key_reused.
  • If the first request is still being processed you get 409 idempotency_in_progress; try again shortly. If a request stalls on our side and never gets a response, its key can be reused after 10 minutes.
  • Only successful responses are stored: if you got an error, fix it and resend with the same key. Keys are scoped to the business.
curl -X POST "https://islemde.app/api/v1/jobs" \
  -H "x-api-key: $ISLEMDE_API_KEY" \
  -H "content-type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "customer": {
    "name": "Emre Kaya",
    "phone": "0544 111 22 33"
  },
  "item": {
    "category": "Telefon",
    "brand": "Samsung",
    "model": "Galaxy S23"
  },
  "problem": "Şarj olmuyor.",
  "priority": "normal"
}'

Errors

Errors come with an HTTP status code and always in the same shape. code never changes, so your program can rely on it; message is readable Turkish you can show to users or write to a log.

Response
{
  "error": {
    "code": "invalid_api_key",
    "message": "API anahtarı geçersiz, süresi dolmuş ya da iptal edilmiş."
  }
}

For a request with an invalid body, issues says which field was rejected and why; path separates nested fields with dots. Unknown fields are rejected too, so typos never get lost silently.

Response
{
  "error": {
    "code": "invalid_input",
    "message": "Gönderilen bilgilerde hata var.",
    "issues": [
      {
        "path": "customer.phone",
        "message": "Geçerli bir telefon numarası girin."
      },
      {
        "path": "problem",
        "message": "Sorunu kısaca yazın."
      }
    ]
  }
}
StatusCodeMeaning
400invalid_inputInvalid parameter or body; issues says which field was rejected and why.
400invalid_jsonThe body isn't valid JSON.
400invalid_idempotency_keyThe Idempotency-Key is malformed.
401missing_api_keyNo key was sent.
401invalid_api_keyThe key is wrong, expired or revoked.
403plan_feature_lockedThe business's plan doesn't include the API.
403insufficient_scopeThe key is read-only.
403workspace_restrictedThe account is read-only because a payment failed; writes are off.
403trial_expiredThe trial has ended; the account is read-only and writes are off.
403plan_requiredThe business needs to pick a plan first; writes are off.
403trial_limit_reachedThe trial's job allowance is used up; no new jobs can be opened.
403overage_cap_reachedThis month's extra usage cap has been reached; no new jobs can be opened.
403subscription_canceledThe subscription has ended; the account is read-only and writes are off.
403forbiddenThe key has no access to this record.
404not_foundNo such record.
409conflictThe record just changed or the request can't be fulfilled right now; fetch the current state and try again.
409phone_in_useThe phone belongs to another customer.
409idempotency_in_progressA request with the same Idempotency-Key is still being processed.
409refund_exceeds_paidThe refund is more than the payments received for the job (minus earlier refunds).
413payload_too_largeThe body is larger than 64 KB.
422idempotency_key_reusedThe Idempotency-Key was used with a different body in the last 24 hours.
429rate_limitedYou've exceeded the per-minute request or write limit; wait the number of seconds in Retry-After.
500internal_errorSomething went wrong on our side; we've logged it.

Limits

Each key can make 600 requests a minute, up to 120 of them writes. Request bodies are capped at 64 KB. Go over the limit and you get 429; wait the number of seconds in the Retry-After header, then carry on. If you poll for changes often, use changedSince to ask only for what changed since your last check instead of rereading everything.

OpenAPI

Every endpoint and field is also available as OpenAPI 3.1. Import it into Postman or Insomnia, or generate a client in your own language. Webhook event bodies are in the document's webhooks section too.

/api/v1/openapi.json

Export all jobs to a spreadsheet

Write every job to a single CSV file for Excel or Google Sheets. No need to think about how many pages there are; the helper fetches them all in turn.

  1. Ask for all open and closed jobs with state=all.
  2. Write the fields you want for each job on one row.
  3. Open the file in Excel; choose UTF-8 so Turkish characters show correctly.
import { writeFile } from "node:fs/promises";
import { all } from "./islemde.js";

const cell = (value) => `"${String(value ?? "").replaceAll('"', '""')}"`;
const rows = [["No", "Müşteri", "Cihaz", "Durum", "Açılış"]];

for await (const job of all("/jobs", { state: "all", limit: 100 })) {
  rows.push([
    job.number,
    job.customer.name,
    [job.item.brand, job.item.model].filter(Boolean).join(" "),
    job.status.label,
    job.createdAt.slice(0, 10),
  ]);
}

await writeFile("isler.csv", rows.map((row) => row.map(cell).join(",")).join("\n"));
isler.csv
No,Müşteri,Cihaz,Durum,Açılış
1057,"Nur Özkan","Apple iPhone 12","Teslime hazır",2026-09-24
1056,"Emre Kaya","Samsung Galaxy S21","Onarımda",2026-09-23

Uses the ready-made client.

Notify your own system about ready jobs

For your own SMS provider, CRM or team chat: every few minutes, fetch the jobs that became ready for pickup since the last check.

  1. Ask only for jobs ready for pickup with status=ready.
  2. Use changedSince to get jobs whose status changed since the last check; the filter runs on our side.
  3. Store the check time and run the script every few minutes with a scheduler (cron). The first run returns every job that's ready.
import { readFile, writeFile } from "node:fs/promises";
import { all } from "./islemde.js";

// Kendi SMS, e-posta ya da sohbet gönderiminiz.
const notify = async (text) => console.log(text);

const since = await readFile("son-kontrol.txt", "utf8").catch(() => undefined);
const now = new Date().toISOString();

for await (const job of all("/jobs", { status: "ready", changedSince: since })) {
  await notify(`#${job.number} ${job.customer.name} için hazır: ${job.trackingUrl}`);
}

await writeFile("son-kontrol.txt", now);
Output
#1047 Nur Özkan için hazır: https://islemde.app/t/U7WX42A8

Uses the ready-made client.

Send the day's handovers to accounting

At the end of the day, list the jobs handed over today with their amounts, payments received and balances due. It's all in the job list; no need to open jobs one by one.

  1. Ask for state=done with changedSince set to the start of today; only jobs closed today come back.
  2. Jobs with an empty deliveredAt were cancelled; skip them.
  3. On each job, chargeKurus is the amount, paidKurus what was received and balanceKurus the balance due. Amounts are in kuruş: 350000 means ₺3,500.00.
  4. For every job handed over but not paid for, list with paymentStatus=owed.
  5. For totals per customer, request /customers?owing=true; each customer's owedKurus is their balance due.
import { all } from "./islemde.js";

const startOfDay = new Date(new Date().setHours(0, 0, 0, 0)).toISOString();
const lira = (kurus) =>
  new Intl.NumberFormat("tr-TR", { style: "currency", currency: "TRY" }).format(kurus / 100);
let paid = 0;
let owed = 0;

for await (const job of all("/jobs", { state: "done", changedSince: startOfDay })) {
  if (!job.deliveredAt) continue; // iptal edilen

  const left = Math.max(0, job.balanceKurus ?? 0); // ücretsizde 0, tutar yoksa null
  paid += job.paidKurus;
  owed += left;
  console.log(job.number, job.customer.name, lira(job.chargeKurus ?? 0), lira(job.paidKurus), lira(left));
}

console.log("Alınan", lira(paid), "· Kalan", lira(owed));
Output
1047 Nur Özkan 3.500,00 TL 3.500,00 TL 0,00 TL
1041 Emre Kaya 1.250,00 TL 500,00 TL 750,00 TL
Alınan 4.000,00 TL · Kalan 750,00 TL

Uses the ready-made client.

List jobs

GET/api/v1/jobs

Newest first, page by page.

Parameters

NameDescription
statestringOpen jobs, closed ones or all of them.activedoneall Default: active.
statusstringOnly jobs in this status; the status key.
qstringSearches job number, customer name, phone or device.
changedSincestringOnly jobs whose status changed after this moment; ISO 8601, e.g. 2026-09-26T00:00:00Z. For polling changes regularly.
paymentStatusstringBy payment status: paid, partial, unpaid, waived, none; balance for jobs with a balance, owed for jobs handed over but not paid for (on credit). With owed, a missing state counts as all.paidpartialunpaidwaivednonebalanceowed
limitintegerRecords per page, 1–100. Default: 25.
cursorstringThe nextCursor from the previous response; leave it out for the first page.
Request
curl "https://islemde.app/api/v1/jobs?status=ready" \
  -H "x-api-key: $ISLEMDE_API_KEY"
Response
{
  "data": [
    {
      "number": 1047,
      "trackingCode": "U7WX42A8",
      "trackingUrl": "https://islemde.app/t/U7WX42A8",
      "status": {
        "key": "ready",
        "label": "Teslime hazır",
        "category": "open"
      },
      "item": {
        "category": "Telefon",
        "brand": "Apple",
        "model": "iPhone 12"
      },
      "problem": "Ekran değişimi.",
      "priority": "normal",
      "customer": {
        "name": "Nur Özkan",
        "phone": "+905321234567"
      },
      "assignee": "Burak Demir",
      "estimateKurus": 350000,
      "chargeKurus": 350000,
      "paidKurus": 100000,
      "balanceKurus": 250000,
      "paymentStatus": "partial",
      "dueAt": "2026-09-27T15:00:00.000Z",
      "createdAt": "2026-09-24T08:12:40.000Z",
      "statusChangedAt": "2026-09-25T13:02:11.000Z",
      "deliveredAt": null,
      "cancelledAt": null
    }
  ],
  "nextCursor": "1046"
}

each record in data

FieldDescription
numberintegerJob number; shown as #1047 in the dashboard.
trackingCodestringThe customer's tracking code (8 characters).
trackingUrlstringThe tracking page you can send to the customer. On your own domain if you've connected one, otherwise on islemde.app; the same in the API and webhooks.
statusobjectCurrent status.
status.keystringThe status key: received, ready, delivered…
status.labelstringIts name in the dashboard.
status.categorystringThe kind of status.openwaitingdonecancelled
itemobjectDevice or product.
item.categorystring · can be nullType, e.g. Phone.
item.brandstring · can be nullBrand.
item.modelstring · can be nullModel.
problemstringThe problem the customer reported.
prioritystringPriority.lownormalhighurgent
customerobjectCustomer.
customer.namestringFull name.
customer.phonestring · can be nullPhone, with +90.
assigneestring · can be nullName of the assignee.
estimateKurusinteger · can be nullThe estimate given at check-in, in kuruş (185000 = ₺1,850.00).
chargeKurusinteger · can be nullThe job's amount in kuruş: the approved quote's total incl. VAT, otherwise the estimate. null on a cancelled job.
paidKurusintegerPayments received minus refunds, in kuruş. Voided records don't count.
balanceKurusinteger · can be nullBalance due in kuruş: chargeKurus − paidKurus. 0 on a job marked free, null if there's no amount yet; negative when overpaid.
paymentStatusstringPayment status: paid, partial (partly paid), unpaid, waived (free / under warranty), none (no amount). Jobs imported by CSV are none until a payment is recorded.paidpartialunpaidwaivednone
dueAtdate (ISO 8601) · can be nullThe promised due date.
createdAtdate (ISO 8601)When it was checked in.
statusChangedAtdate (ISO 8601)When the status last changed.
deliveredAtdate (ISO 8601) · can be nullWhen it was handed over.
cancelledAtdate (ISO 8601) · can be nullWhen it was cancelled.

Get a job

GET/api/v1/jobs/{number}

The job and the latest page of its history: up to 30 events, 10 quotes and 10 warranties. The timeline only holds events the customer can see, so it may be empty. For each list, send the nextCursor or prevCursor from pagination in the matching query parameter; null means there are no more pages in that direction.

Parameters

NameDescription
numberpath, requiredJob number.
eventsCursorstringnextCursor or prevCursor from pagination.events; leave it out for the first page. Up to 2048 characters.
quotesCursorstringnextCursor or prevCursor from pagination.quotes; leave it out for the first page. Up to 2048 characters.
warrantiesCursorstringnextCursor or prevCursor from pagination.warranties; leave it out for the first page. Up to 2048 characters.
Request
curl "https://islemde.app/api/v1/jobs/1047" \
  -H "x-api-key: $ISLEMDE_API_KEY"
Response
{
  "pagination": {
    "events": {
      "nextCursor": null,
      "prevCursor": null
    },
    "quotes": {
      "nextCursor": null,
      "prevCursor": null
    },
    "warranties": {
      "nextCursor": null,
      "prevCursor": null
    }
  },
  "data": {
    "number": 1047,
    "trackingCode": "U7WX42A8",
    "trackingUrl": "https://islemde.app/t/U7WX42A8",
    "status": {
      "key": "ready",
      "label": "Teslime hazır",
      "category": "open"
    },
    "item": {
      "category": "Telefon",
      "brand": "Apple",
      "model": "iPhone 12"
    },
    "problem": "Ekran değişimi.",
    "priority": "normal",
    "customer": {
      "name": "Nur Özkan",
      "phone": "+905321234567",
      "email": null
    },
    "assignee": "Burak Demir",
    "estimateKurus": 350000,
    "chargeKurus": 350000,
    "paidKurus": 100000,
    "balanceKurus": 250000,
    "paymentStatus": "partial",
    "dueAt": "2026-09-27T15:00:00.000Z",
    "createdAt": "2026-09-24T08:12:40.000Z",
    "statusChangedAt": "2026-09-25T13:02:11.000Z",
    "deliveredAt": null,
    "cancelledAt": null,
    "serial": "356789104455120",
    "accessories": "Kılıf",
    "timeline": [
      {
        "type": "created",
        "at": "2026-09-24T08:12:40.000Z",
        "text": "Cihazınız teslim alındı"
      },
      {
        "type": "status_changed",
        "at": "2026-09-25T13:02:11.000Z",
        "text": "Teslime hazır"
      }
    ],
    "quotes": [
      {
        "number": 212,
        "status": "approved",
        "totalKurus": 350000,
        "validUntil": "2026-10-01T00:00:00.000Z"
      }
    ],
    "warranties": []
  }
}

data

FieldDescription
numberintegerJob number; shown as #1047 in the dashboard.
trackingCodestringThe customer's tracking code (8 characters).
trackingUrlstringThe tracking page you can send to the customer. On your own domain if you've connected one, otherwise on islemde.app; the same in the API and webhooks.
statusobjectCurrent status.
status.keystringThe status key: received, ready, delivered…
status.labelstringIts name in the dashboard.
status.categorystringThe kind of status.openwaitingdonecancelled
itemobjectDevice or product.
item.categorystring · can be nullType, e.g. Phone.
item.brandstring · can be nullBrand.
item.modelstring · can be nullModel.
problemstringThe problem the customer reported.
prioritystringPriority.lownormalhighurgent
customerobjectCustomer.
customer.namestringFull name.
customer.phonestring · can be nullPhone, with +90.
customer.emailstring · can be nullEmail.
assigneestring · can be nullName of the assignee.
estimateKurusinteger · can be nullThe estimate given at check-in, in kuruş (185000 = ₺1,850.00).
chargeKurusinteger · can be nullThe job's amount in kuruş: the approved quote's total incl. VAT, otherwise the estimate. null on a cancelled job.
paidKurusintegerPayments received minus refunds, in kuruş. Voided records don't count.
balanceKurusinteger · can be nullBalance due in kuruş: chargeKurus − paidKurus. 0 on a job marked free, null if there's no amount yet; negative when overpaid.
paymentStatusstringPayment status: paid, partial (partly paid), unpaid, waived (free / under warranty), none (no amount). Jobs imported by CSV are none until a payment is recorded.paidpartialunpaidwaivednone
dueAtdate (ISO 8601) · can be nullThe promised due date.
createdAtdate (ISO 8601)When it was checked in.
statusChangedAtdate (ISO 8601)When the status last changed.
deliveredAtdate (ISO 8601) · can be nullWhen it was handed over.
cancelledAtdate (ISO 8601) · can be nullWhen it was cancelled.
serialstring · can be nullSerial or IMEI number.
accessoriesstring · can be nullItems left with the device, e.g. case, charger.
timeline[]listThe history the customer also sees, oldest first. Internal notes aren't included.
timeline[].typestringcreated, status_changed, quote_sent…
timeline[].atdate (ISO 8601)When it happened.
timeline[].textstringThe sentence the customer sees on the tracking page, e.g. Ready for pickup.
quotes[]listQuotes.
quotes[].numberintegerQuote number.
quotes[].statusstringIts status.draftsentapprovedrejectedexpiredcancelled
quotes[].totalKurusintegerTotal incl. VAT, in kuruş.
quotes[].validUntildate (ISO 8601) · can be nullValid until.
warranties[]listWarranty certificates.
warranties[].codestringCertificate code.
warranties[].statusstringactive or void.
warranties[].monthsintegerLength, in months.
warranties[].endsAtdate (ISO 8601)End date.

Create a job

POST/api/v1/jobs

Opens a job for an existing customer or with a new one. As in the dashboard, it counts toward your job allowance, the customer gets the check-in SMS and a job.created event is sent.

Needs a read and write key. Safe to retry with an Idempotency-Key.

Body (JSON)

FieldDescription
customerIdstringThe existing customer's ID. Send either customerId or customer.
customerobjectA new customer. If a customer with the same phone exists, no new one is created and the job is linked to them.
customer.namestring · requiredFull name or company name, 2–120 characters.
customer.phonestringPhone; 0532 123 45 67, 5321234567 or +905321234567 all work.
customer.emailstringEmail.
customer.kindstringIndividual or business. Default: individual.individualcompany
customer.taxIdstringTax number (10 digits) or Turkish ID number (11 digits). Never returned.
customer.taxOfficestringTax office.
customer.citystringProvince, spelled in Turkish: İstanbul, İzmir…
customer.districtstringDistrict; send it together with the province.
customer.addressstringStreet address, up to 300 characters.
customer.notesstringA note about the customer for your team. Never returned.
itemobjectDevice or product.
item.categorystringType, e.g. Phone.
item.brandstringBrand.
item.modelstringModel.
problemstring · requiredThe problem the customer reported, 3–2000 characters.
serialstringSerial or IMEI number.
accessoriesstringItems left with the device.
intakeConditionstringIts condition at check-in: scratches, cracks…
estimateKurusintegerEstimated amount in kuruş (185000 = ₺1,850.00).
prioritystringPriority. Default: normal.lownormalhighurgent
dueAtdate (ISO 8601)The promised due date; ISO 8601.
privateNotestringA team-only note. The API and webhooks never send it back.
Request
curl -X POST "https://islemde.app/api/v1/jobs" \
  -H "x-api-key: $ISLEMDE_API_KEY" \
  -H "content-type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "customer": {
    "name": "Emre Kaya",
    "phone": "0544 111 22 33"
  },
  "item": {
    "category": "Telefon",
    "brand": "Samsung",
    "model": "Galaxy S23"
  },
  "problem": "Şarj olmuyor.",
  "priority": "normal"
}'
Response
{
  "data": {
    "number": 1058,
    "trackingCode": "K3PX9M2D",
    "trackingUrl": "https://islemde.app/t/K3PX9M2D",
    "status": {
      "key": "received",
      "label": "Teslim alındı",
      "category": "open"
    },
    "item": {
      "category": "Telefon",
      "brand": "Samsung",
      "model": "Galaxy S23"
    },
    "problem": "Şarj olmuyor.",
    "priority": "normal",
    "customer": {
      "name": "Emre Kaya",
      "phone": "+905441112233"
    },
    "assignee": null,
    "estimateKurus": null,
    "chargeKurus": null,
    "paidKurus": 0,
    "balanceKurus": null,
    "paymentStatus": "none",
    "dueAt": null,
    "createdAt": "2026-09-27T09:30:00.000Z",
    "statusChangedAt": "2026-09-27T09:30:00.000Z",
    "deliveredAt": null,
    "cancelledAt": null
  }
}

data

FieldDescription
numberintegerJob number; shown as #1047 in the dashboard.
trackingCodestringThe customer's tracking code (8 characters).
trackingUrlstringThe tracking page you can send to the customer. On your own domain if you've connected one, otherwise on islemde.app; the same in the API and webhooks.
statusobjectCurrent status.
status.keystringThe status key: received, ready, delivered…
status.labelstringIts name in the dashboard.
status.categorystringThe kind of status.openwaitingdonecancelled
itemobjectDevice or product.
item.categorystring · can be nullType, e.g. Phone.
item.brandstring · can be nullBrand.
item.modelstring · can be nullModel.
problemstringThe problem the customer reported.
prioritystringPriority.lownormalhighurgent
customerobjectCustomer.
customer.namestringFull name.
customer.phonestring · can be nullPhone, with +90.
assigneestring · can be nullName of the assignee.
estimateKurusinteger · can be nullThe estimate given at check-in, in kuruş (185000 = ₺1,850.00).
chargeKurusinteger · can be nullThe job's amount in kuruş: the approved quote's total incl. VAT, otherwise the estimate. null on a cancelled job.
paidKurusintegerPayments received minus refunds, in kuruş. Voided records don't count.
balanceKurusinteger · can be nullBalance due in kuruş: chargeKurus − paidKurus. 0 on a job marked free, null if there's no amount yet; negative when overpaid.
paymentStatusstringPayment status: paid, partial (partly paid), unpaid, waived (free / under warranty), none (no amount). Jobs imported by CSV are none until a payment is recorded.paidpartialunpaidwaivednone
dueAtdate (ISO 8601) · can be nullThe promised due date.
createdAtdate (ISO 8601)When it was checked in.
statusChangedAtdate (ISO 8601)When the status last changed.
deliveredAtdate (ISO 8601) · can be nullWhen it was handed over.
cancelledAtdate (ISO 8601) · can be nullWhen it was cancelled.

Update a job

PATCH/api/v1/jobs/{number}

Only the fields you send change; a field sent as null is cleared. The change is logged in the job's history for the team; the signed check-in ticket stays as it was.

Needs a read and write key.

Parameters

NameDescription
numberpath, requiredJob number.

Body (JSON)

FieldDescription
itemobjectDevice or product.
item.categorystringType, e.g. Phone.
item.brandstringBrand.
item.modelstringModel.
problemstringThe problem the customer reported, 3–2000 characters.
serialstring · null clears itSerial or IMEI number.
accessoriesstring · null clears itItems left with the device.
intakeConditionstring · null clears itIts condition at check-in: scratches, cracks…
estimateKurusinteger · null clears itEstimated amount in kuruş (185000 = ₺1,850.00).
prioritystringPriority. Default: normal.lownormalhighurgent
dueAtdate (ISO 8601) · null clears itThe promised due date; ISO 8601.
privateNotestring · null clears itA team-only note. The API and webhooks never send it back.
Request
curl -X PATCH "https://islemde.app/api/v1/jobs/1047" \
  -H "x-api-key: $ISLEMDE_API_KEY" \
  -H "content-type: application/json" \
  -d '{
  "item": {
    "model": "iPhone 12 Pro"
  },
  "estimateKurus": 420000,
  "dueAt": null
}'
Response
{
  "data": {
    "number": 1047,
    "trackingCode": "U7WX42A8",
    "trackingUrl": "https://islemde.app/t/U7WX42A8",
    "status": {
      "key": "ready",
      "label": "Teslime hazır",
      "category": "open"
    },
    "item": {
      "category": "Telefon",
      "brand": "Apple",
      "model": "iPhone 12 Pro"
    },
    "problem": "Ekran değişimi.",
    "priority": "normal",
    "customer": {
      "name": "Nur Özkan",
      "phone": "+905321234567"
    },
    "assignee": "Burak Demir",
    "estimateKurus": 420000,
    "chargeKurus": 350000,
    "paidKurus": 100000,
    "balanceKurus": 250000,
    "paymentStatus": "partial",
    "dueAt": null,
    "createdAt": "2026-09-24T08:12:40.000Z",
    "statusChangedAt": "2026-09-25T13:02:11.000Z",
    "deliveredAt": null,
    "cancelledAt": null
  }
}

data

FieldDescription
numberintegerJob number; shown as #1047 in the dashboard.
trackingCodestringThe customer's tracking code (8 characters).
trackingUrlstringThe tracking page you can send to the customer. On your own domain if you've connected one, otherwise on islemde.app; the same in the API and webhooks.
statusobjectCurrent status.
status.keystringThe status key: received, ready, delivered…
status.labelstringIts name in the dashboard.
status.categorystringThe kind of status.openwaitingdonecancelled
itemobjectDevice or product.
item.categorystring · can be nullType, e.g. Phone.
item.brandstring · can be nullBrand.
item.modelstring · can be nullModel.
problemstringThe problem the customer reported.
prioritystringPriority.lownormalhighurgent
customerobjectCustomer.
customer.namestringFull name.
customer.phonestring · can be nullPhone, with +90.
assigneestring · can be nullName of the assignee.
estimateKurusinteger · can be nullThe estimate given at check-in, in kuruş (185000 = ₺1,850.00).
chargeKurusinteger · can be nullThe job's amount in kuruş: the approved quote's total incl. VAT, otherwise the estimate. null on a cancelled job.
paidKurusintegerPayments received minus refunds, in kuruş. Voided records don't count.
balanceKurusinteger · can be nullBalance due in kuruş: chargeKurus − paidKurus. 0 on a job marked free, null if there's no amount yet; negative when overpaid.
paymentStatusstringPayment status: paid, partial (partly paid), unpaid, waived (free / under warranty), none (no amount). Jobs imported by CSV are none until a payment is recorded.paidpartialunpaidwaivednone
dueAtdate (ISO 8601) · can be nullThe promised due date.
createdAtdate (ISO 8601)When it was checked in.
statusChangedAtdate (ISO 8601)When the status last changed.
deliveredAtdate (ISO 8601) · can be nullWhen it was handed over.
cancelledAtdate (ISO 8601) · can be nullWhen it was cancelled.

Change status

POST/api/v1/jobs/{number}/status

Moves the job to another stage. The customer's SMS and email go out just as if it were changed in the dashboard; delivered hands the job over. Get status keys from the Statuses endpoint.

Needs a read and write key. Safe to retry with an Idempotency-Key.

Parameters

NameDescription
numberpath, requiredJob number.

Body (JSON)

FieldDescription
statusstringThe status key, e.g. ready. Send either status or statusId.
statusIdstringThe status's ID.
Request
curl -X POST "https://islemde.app/api/v1/jobs/1047/status" \
  -H "x-api-key: $ISLEMDE_API_KEY" \
  -H "content-type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "status": "ready"
}'
Response
{
  "data": {
    "number": 1047,
    "trackingCode": "U7WX42A8",
    "trackingUrl": "https://islemde.app/t/U7WX42A8",
    "status": {
      "key": "ready",
      "label": "Teslime hazır",
      "category": "open"
    },
    "item": {
      "category": "Telefon",
      "brand": "Apple",
      "model": "iPhone 12"
    },
    "problem": "Ekran değişimi.",
    "priority": "normal",
    "customer": {
      "name": "Nur Özkan",
      "phone": "+905321234567"
    },
    "assignee": "Burak Demir",
    "estimateKurus": 350000,
    "chargeKurus": 350000,
    "paidKurus": 100000,
    "balanceKurus": 250000,
    "paymentStatus": "partial",
    "dueAt": "2026-09-27T15:00:00.000Z",
    "createdAt": "2026-09-24T08:12:40.000Z",
    "statusChangedAt": "2026-09-25T13:02:11.000Z",
    "deliveredAt": null,
    "cancelledAt": null
  }
}

data

FieldDescription
numberintegerJob number; shown as #1047 in the dashboard.
trackingCodestringThe customer's tracking code (8 characters).
trackingUrlstringThe tracking page you can send to the customer. On your own domain if you've connected one, otherwise on islemde.app; the same in the API and webhooks.
statusobjectCurrent status.
status.keystringThe status key: received, ready, delivered…
status.labelstringIts name in the dashboard.
status.categorystringThe kind of status.openwaitingdonecancelled
itemobjectDevice or product.
item.categorystring · can be nullType, e.g. Phone.
item.brandstring · can be nullBrand.
item.modelstring · can be nullModel.
problemstringThe problem the customer reported.
prioritystringPriority.lownormalhighurgent
customerobjectCustomer.
customer.namestringFull name.
customer.phonestring · can be nullPhone, with +90.
assigneestring · can be nullName of the assignee.
estimateKurusinteger · can be nullThe estimate given at check-in, in kuruş (185000 = ₺1,850.00).
chargeKurusinteger · can be nullThe job's amount in kuruş: the approved quote's total incl. VAT, otherwise the estimate. null on a cancelled job.
paidKurusintegerPayments received minus refunds, in kuruş. Voided records don't count.
balanceKurusinteger · can be nullBalance due in kuruş: chargeKurus − paidKurus. 0 on a job marked free, null if there's no amount yet; negative when overpaid.
paymentStatusstringPayment status: paid, partial (partly paid), unpaid, waived (free / under warranty), none (no amount). Jobs imported by CSV are none until a payment is recorded.paidpartialunpaidwaivednone
dueAtdate (ISO 8601) · can be nullThe promised due date.
createdAtdate (ISO 8601)When it was checked in.
statusChangedAtdate (ISO 8601)When the status last changed.
deliveredAtdate (ISO 8601) · can be nullWhen it was handed over.
cancelledAtdate (ISO 8601) · can be nullWhen it was cancelled.

Add a note

POST/api/v1/jobs/{number}/notes

Adds a note to the job. Only your team sees it by default; with customerVisible it also shows on the customer's tracking page.

Needs a read and write key. Safe to retry with an Idempotency-Key.

Parameters

NameDescription
numberpath, requiredJob number.

Body (JSON)

FieldDescription
bodystring · requiredThe note, 1–4000 characters.
customerVisibletrue/falseShow it to the customer too. Default: false.
Request
curl -X POST "https://islemde.app/api/v1/jobs/1047/notes" \
  -H "x-api-key: $ISLEMDE_API_KEY" \
  -H "content-type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "body": "Parça siparişi verildi, perşembe gelecek.",
  "customerVisible": true
}'
Response
{
  "data": {
    "id": "0f9c2a64-7d0e-4b8f-a0f1-65e0b5d2c7a3",
    "body": "Parça siparişi verildi, perşembe gelecek.",
    "customerVisible": true,
    "author": "API · Muhasebe",
    "createdAt": "2026-09-27T10:05:00.000Z"
  }
}

data

FieldDescription
idstringThe note's ID.
bodystringNote.
customerVisibletrue/falseAlso shown on the customer's tracking page.
authorstringAuthor; the key's name for notes from the API.
createdAtdate (ISO 8601)When it was added.

List a job's payments

GET/api/v1/jobs/{number}/payments

Payments and refunds recorded on the job, newest first; voided ones include voidedAt. The remaining balance is in the job's balanceKurus field.

Parameters

NameDescription
numberpath, requiredJob number.
Request
curl "https://islemde.app/api/v1/jobs/1047/payments" \
  -H "x-api-key: $ISLEMDE_API_KEY"
Response
{
  "data": [
    {
      "id": "8d3f0c1e-54a2-4a71-9b7e-2f6a1c0d9e44",
      "kind": "payment",
      "amountKurus": 100000,
      "method": "cash",
      "note": "Kapora",
      "receivedAt": "2026-09-24T08:14:02.000Z",
      "receivedBy": "Selin Aydın",
      "atHandover": false,
      "voidedAt": null,
      "voidReason": null
    }
  ]
}

each record in data

FieldDescription
idstringThe record's ID.
kindstringpayment for a payment received, refund for a refund to the customer.paymentrefund
amountKurusintegerAmount in kuruş; always positive.
methodstringcash, card (the business's own POS), transfer (bank transfer / EFT), other.cashcardtransferother
notestring · can be nullNote.
receivedAtdate (ISO 8601)When the payment was received.
receivedBystring · can be nullWho recorded it; the key's name for records from the API.
atHandovertrue/falseTaken at pickup.
voidedAtdate (ISO 8601) · can be nullWhen it was voided, if it was; voided records don't count.
voidReasonstring · can be nullReason for voiding.

Record a payment

POST/api/v1/jobs/{number}/payments

Records a payment or refund; the job's balance, cash register and reports update just as in the dashboard, and a payment.recorded event is sent. İşlemde doesn't charge anyone, it only records the payment you received. Send the same Idempotency-Key when retrying; within 24 hours a payment is never recorded twice.

Needs a read and write key. Safe to retry with an Idempotency-Key.

Parameters

NameDescription
numberpath, requiredJob number.

Body (JSON)

FieldDescription
kindstringpayment or refund. Default: payment.paymentrefund
amountKurusinteger · requiredAmount in kuruş; between 1 and 100000000 (₺1,000,000).
methodstring · requiredPayment method.cashcardtransferother
notestringNote, up to 300 characters.
Request
curl -X POST "https://islemde.app/api/v1/jobs/1047/payments" \
  -H "x-api-key: $ISLEMDE_API_KEY" \
  -H "content-type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "amountKurus": 250000,
  "method": "card",
  "note": "Kalan ödeme"
}'
Response
{
  "data": {
    "payment": {
      "id": "c41b7a2e-0f3d-4e8a-9a55-7d21e6b0f913",
      "kind": "payment",
      "amountKurus": 250000,
      "method": "card",
      "note": "Kalan ödeme",
      "receivedAt": "2026-09-27T16:40:12.000Z",
      "receivedBy": "API · Muhasebe",
      "atHandover": false,
      "voidedAt": null,
      "voidReason": null
    },
    "job": {
      "number": 1047,
      "trackingCode": "U7WX42A8",
      "trackingUrl": "https://islemde.app/t/U7WX42A8",
      "status": {
        "key": "ready",
        "label": "Teslime hazır",
        "category": "open"
      },
      "item": {
        "category": "Telefon",
        "brand": "Apple",
        "model": "iPhone 12"
      },
      "problem": "Ekran değişimi.",
      "priority": "normal",
      "customer": {
        "name": "Nur Özkan",
        "phone": "+905321234567"
      },
      "assignee": "Burak Demir",
      "estimateKurus": 350000,
      "chargeKurus": 350000,
      "paidKurus": 350000,
      "balanceKurus": 0,
      "paymentStatus": "paid",
      "dueAt": "2026-09-27T15:00:00.000Z",
      "createdAt": "2026-09-24T08:12:40.000Z",
      "statusChangedAt": "2026-09-25T13:02:11.000Z",
      "deliveredAt": null,
      "cancelledAt": null
    }
  }
}

data

FieldDescription
paymentobjectThe record.
payment.idstringThe record's ID.
payment.kindstringpayment for a payment received, refund for a refund to the customer.paymentrefund
payment.amountKurusintegerAmount in kuruş; always positive.
payment.methodstringcash, card (the business's own POS), transfer (bank transfer / EFT), other.cashcardtransferother
payment.notestring · can be nullNote.
payment.receivedAtdate (ISO 8601)When the payment was received.
payment.receivedBystring · can be nullWho recorded it; the key's name for records from the API.
payment.atHandovertrue/falseTaken at pickup.
payment.voidedAtdate (ISO 8601) · can be nullWhen it was voided, if it was; voided records don't count.
payment.voidReasonstring · can be nullReason for voiding.
jobobjectThe job after the record; balanceKurus gives the remaining balance.
job.numberintegerJob number; shown as #1047 in the dashboard.
job.trackingCodestringThe customer's tracking code (8 characters).
job.trackingUrlstringThe tracking page you can send to the customer. On your own domain if you've connected one, otherwise on islemde.app; the same in the API and webhooks.
job.statusobjectCurrent status.
job.status.keystringThe status key: received, ready, delivered…
job.status.labelstringIts name in the dashboard.
job.status.categorystringThe kind of status.openwaitingdonecancelled
job.itemobjectDevice or product.
job.item.categorystring · can be nullType, e.g. Phone.
job.item.brandstring · can be nullBrand.
job.item.modelstring · can be nullModel.
job.problemstringThe problem the customer reported.
job.prioritystringPriority.lownormalhighurgent
job.customerobjectCustomer.
job.customer.namestringFull name.
job.customer.phonestring · can be nullPhone, with +90.
job.assigneestring · can be nullName of the assignee.
job.estimateKurusinteger · can be nullThe estimate given at check-in, in kuruş (185000 = ₺1,850.00).
job.chargeKurusinteger · can be nullThe job's amount in kuruş: the approved quote's total incl. VAT, otherwise the estimate. null on a cancelled job.
job.paidKurusintegerPayments received minus refunds, in kuruş. Voided records don't count.
job.balanceKurusinteger · can be nullBalance due in kuruş: chargeKurus − paidKurus. 0 on a job marked free, null if there's no amount yet; negative when overpaid.
job.paymentStatusstringPayment status: paid, partial (partly paid), unpaid, waived (free / under warranty), none (no amount). Jobs imported by CSV are none until a payment is recorded.paidpartialunpaidwaivednone
job.dueAtdate (ISO 8601) · can be nullThe promised due date.
job.createdAtdate (ISO 8601)When it was checked in.
job.statusChangedAtdate (ISO 8601)When the status last changed.
job.deliveredAtdate (ISO 8601) · can be nullWhen it was handed over.
job.cancelledAtdate (ISO 8601) · can be nullWhen it was cancelled.

List customers

GET/api/v1/customers

Starting with the most recent job, page by page.

Parameters

NameDescription
qstringSearches name, phone or email.
owingstringWith true, only customers who still owe on handed-over jobs.truefalse Default: false.
limitintegerRecords per page, 1–100. Default: 25.
cursorstringThe nextCursor from the previous response; leave it out for the first page.
Request
curl "https://islemde.app/api/v1/customers" \
  -H "x-api-key: $ISLEMDE_API_KEY"
Response
{
  "data": [
    {
      "id": "c30f5268-a5a7-4269-a9c0-bc135f6a90ae",
      "name": "Nur Özkan",
      "phone": "+905321234567",
      "email": null,
      "jobCount": 3,
      "lastJobAt": "2026-09-24T08:12:40.000Z",
      "owedKurus": 250000,
      "createdAt": "2026-06-02T10:40:00.000Z"
    }
  ],
  "nextCursor": null
}

each record in data

FieldDescription
idstringThe customer's ID.
namestringFull name.
phonestring · can be nullPhone, with +90.
emailstring · can be nullEmail.
jobCountintegerTotal number of jobs.
lastJobAtdate (ISO 8601) · can be nullWhen the latest job was opened.
owedKurusintegerBalance owed on jobs handed over but not paid for (on credit), in kuruş. Jobs marked free and jobs imported by CSV don't count; 0 if nothing is owed.
createdAtdate (ISO 8601)When the record was created.

Get a customer

GET/api/v1/customers/{id}

The customer's record, job count and balance owed on handed-over jobs.

Parameters

NameDescription
idpath, requiredThe customer's ID.
Request
curl "https://islemde.app/api/v1/customers/5b1e7f7a-0c1d-4a55-9d51-2f0c8f3b8a10" \
  -H "x-api-key: $ISLEMDE_API_KEY"
Response
{
  "data": {
    "id": "5b1e7f7a-0c1d-4a55-9d51-2f0c8f3b8a10",
    "kind": "individual",
    "name": "Emre Kaya",
    "phone": "+905441112233",
    "email": "emre@example.com",
    "city": "İzmir",
    "district": "Karşıyaka",
    "address": null,
    "smsOptOut": false,
    "createdAt": "2026-09-27T09:30:00.000Z",
    "jobCount": 2,
    "owedKurus": 0
  }
}

data

FieldDescription
idstringThe customer's ID.
kindstringIndividual or business.individualcompany
namestringFull name or company name.
phonestring · can be nullPhone, with +90.
emailstring · can be nullEmail.
citystring · can be nullProvince.
districtstring · can be nullDistrict.
addressstring · can be nullStreet address.
smsOptOuttrue/falseThe customer doesn't want SMS messages.
createdAtdate (ISO 8601)When the record was created.
jobCountintegerTotal number of jobs.
owedKurusintegerBalance owed on jobs handed over but not paid for (on credit), in kuruş. Jobs marked free and jobs imported by CSV don't count; 0 if nothing is owed.

Add a customer

POST/api/v1/customers

Creates a new customer and sends a customer.created event. If a customer with the same phone exists it returns 409; search for them by phone with List customers.

Needs a read and write key. Safe to retry with an Idempotency-Key.

Body (JSON)

FieldDescription
namestring · requiredFull name or company name, 2–120 characters.
phonestringPhone; 0532 123 45 67, 5321234567 or +905321234567 all work.
emailstringEmail.
kindstringIndividual or business. Default: individual.individualcompany
taxIdstringTax number (10 digits) or Turkish ID number (11 digits). Never returned.
taxOfficestringTax office.
citystringProvince, spelled in Turkish: İstanbul, İzmir…
districtstringDistrict; send it together with the province.
addressstringStreet address, up to 300 characters.
notesstringA note about the customer for your team. Never returned.
Request
curl -X POST "https://islemde.app/api/v1/customers" \
  -H "x-api-key: $ISLEMDE_API_KEY" \
  -H "content-type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "name": "Emre Kaya",
  "phone": "0544 111 22 33",
  "email": "emre@example.com",
  "city": "İzmir",
  "district": "Karşıyaka"
}'
Response
{
  "data": {
    "id": "5b1e7f7a-0c1d-4a55-9d51-2f0c8f3b8a10",
    "kind": "individual",
    "name": "Emre Kaya",
    "phone": "+905441112233",
    "email": "emre@example.com",
    "city": "İzmir",
    "district": "Karşıyaka",
    "address": null,
    "smsOptOut": false,
    "createdAt": "2026-09-27T09:30:00.000Z"
  }
}

data

FieldDescription
idstringThe customer's ID.
kindstringIndividual or business.individualcompany
namestringFull name or company name.
phonestring · can be nullPhone, with +90.
emailstring · can be nullEmail.
citystring · can be nullProvince.
districtstring · can be nullDistrict.
addressstring · can be nullStreet address.
smsOptOuttrue/falseThe customer doesn't want SMS messages.
createdAtdate (ISO 8601)When the record was created.

Update a customer

PATCH/api/v1/customers/{id}

Only the fields you send change; a field sent as null is cleared. A customer whose personal data has been erased can't be changed.

Needs a read and write key.

Parameters

NameDescription
idpath, requiredThe customer's ID.

Body (JSON)

FieldDescription
namestringFull name or company name, 2–120 characters.
phonestring · null clears itPhone; 0532 123 45 67, 5321234567 or +905321234567 all work.
emailstring · null clears itEmail.
kindstringIndividual or business.individualcompany
taxIdstring · null clears itTax number (10 digits) or Turkish ID number (11 digits). Never returned.
taxOfficestring · null clears itTax office.
citystring · null clears itProvince, spelled in Turkish: İstanbul, İzmir…
districtstring · null clears itDistrict; send it together with the province.
addressstring · null clears itStreet address, up to 300 characters.
notesstring · null clears itA note about the customer for your team. Never returned.
smsOptOuttrue/falseThe customer doesn't want SMS messages.
Request
curl -X PATCH "https://islemde.app/api/v1/customers/5b1e7f7a-0c1d-4a55-9d51-2f0c8f3b8a10" \
  -H "x-api-key: $ISLEMDE_API_KEY" \
  -H "content-type: application/json" \
  -d '{
  "email": "emre.kaya@example.com",
  "smsOptOut": true
}'
Response
{
  "data": {
    "id": "5b1e7f7a-0c1d-4a55-9d51-2f0c8f3b8a10",
    "kind": "individual",
    "name": "Emre Kaya",
    "phone": "+905441112233",
    "email": "emre.kaya@example.com",
    "city": "İzmir",
    "district": "Karşıyaka",
    "address": null,
    "smsOptOut": true,
    "createdAt": "2026-09-27T09:30:00.000Z"
  }
}

data

FieldDescription
idstringThe customer's ID.
kindstringIndividual or business.individualcompany
namestringFull name or company name.
phonestring · can be nullPhone, with +90.
emailstring · can be nullEmail.
citystring · can be nullProvince.
districtstring · can be nullDistrict.
addressstring · can be nullStreet address.
smsOptOuttrue/falseThe customer doesn't want SMS messages.
createdAtdate (ISO 8601)When the record was created.

Statuses

GET/api/v1/statuses

The stages in the business's workflow, in order. Use these keys when changing status and filtering jobs; they don't change even if the business renames a stage.

Request
curl "https://islemde.app/api/v1/statuses" \
  -H "x-api-key: $ISLEMDE_API_KEY"
Response
{
  "data": [
    {
      "id": "d2b8c1e0-2f4a-4c1b-9a77-1f5e3c9b0a11",
      "key": "received",
      "label": "Teslim alındı",
      "customerLabel": "Cihazınız teslim alındı",
      "category": "open",
      "isInitial": true
    },
    {
      "id": "8a3f6d52-6b1e-4f0d-8c3e-7e2a9b4c5d22",
      "key": "ready",
      "label": "Teslime hazır",
      "customerLabel": "Teslime hazır",
      "category": "open",
      "isInitial": false
    },
    {
      "id": "4c7e9a13-0d2b-4e6f-b1a8-3f5c7d9e1b33",
      "key": "delivered",
      "label": "Teslim edildi",
      "customerLabel": "Teslim edildi",
      "category": "done",
      "isInitial": false
    }
  ]
}

each record in data

FieldDescription
idstringThe status's ID.
keystringA key that never changes: received, repairing, ready, delivered, cancelled…
labelstringIts name in the dashboard.
customerLabelstringIts name on the customer's tracking page.
categorystringThe kind of status.openwaitingdonecancelled
isInitialtrue/falseNew jobs open with this status.

How webhooks work

Instead of polling, get changes the moment they happen: when a job is opened, its status changes or a customer approves a quote, İşlemde sends a POST request to your URL. Add the URL under Settings → API → Webhook URLs and choose the events you want. The signing secret (whsec_…) is shown only once.

  • The URL must start with https:// and use port 443 or 1024 and up (8443, for example). It can't contain a username or password; URLs that resolve to internal, local or reserved IP addresses are rejected and checked again on every delivery.
  • The body is the event type plus the same records as the REST API. Team-only notes, tax numbers and approval links are never sent.
  • Every body includes apiVersion (currently 2026-10-01). Adding fields doesn't change the version; if a field is removed or its meaning changes, it gets a new date.
  • The Send test button sends a ping event to your URL; recent deliveries, response codes and errors show on the same page.
Example
{
  "id": "evt_3f9a1c2b7d8e4f60a1b2c3d4e5f60718",
  "type": "job.status_changed",
  "apiVersion": "2026-10-01",
  "createdAt": "2026-09-25T13:02:11.482Z",
  "data": {
    "job": {
      "number": 1047,
      "trackingCode": "U7WX42A8",
      "trackingUrl": "https://islemde.app/t/U7WX42A8",
      "status": {
        "key": "ready",
        "label": "Teslime hazır",
        "category": "open"
      },
      "item": {
        "category": "Telefon",
        "brand": "Apple",
        "model": "iPhone 12"
      },
      "problem": "Ekran değişimi.",
      "priority": "normal",
      "customer": {
        "name": "Nur Özkan",
        "phone": "+905321234567"
      },
      "assignee": "Burak Demir",
      "estimateKurus": 350000,
      "chargeKurus": 350000,
      "paidKurus": 100000,
      "balanceKurus": 250000,
      "paymentStatus": "partial",
      "dueAt": "2026-09-27T15:00:00.000Z",
      "createdAt": "2026-09-24T08:12:40.000Z",
      "statusChangedAt": "2026-09-25T13:02:11.000Z",
      "deliveredAt": null,
      "cancelledAt": null
    },
    "previousStatus": {
      "key": "repairing",
      "label": "Onarımda",
      "category": "open"
    }
  }
}
HeaderMeaning
islemde-eventThe event type, e.g. job.status_changed. ping for a test delivery.
islemde-deliveryThis delivery's ID; it stays the same across retries.
islemde-signaturet=<unix seconds>,v1=<signature>; for 24 hours after the secret is rotated there are two v1 values. Verification below.
content-typeapplication/json
user-agentIslemde-Webhooks/1.0

Events

The job in data is the same job record as in the List jobs endpoint; customer matches the Add customer response.

EventWhendata
job.createdWhen a job is opened in the dashboard, through the API or from a check-in ticket.job
job.updatedWhen a job's details are edited. Not sent if only the team-only note changed.job, changes (the fields that changed)
job.status_changedWhen a job moves to another status, including automatic moves after a quote is approved.job, previousStatus
job.deliveredWhen the device is handed over. Sent again if the handover is redone.job, handover (who collected it, payment, whether free, signature; null when handed over by a status change)
job.note_addedWhen a note visible to the customer is added. Internal notes aren't sent.job, note
quote.sentWhen a quote is sent to the customer.job, quote
quote.approvedWhen the customer signs and approves a quote.job, quote
quote.rejectedWhen the customer declines a quote.job, quote
warranty.issuedWhen a warranty certificate is issued.job, warranty
payment.recordedWhen a payment or refund is recorded: a deposit, at pickup or later, or through the API.job (balanceKurus: the balance after the record), payment
payment.voidedWhen a mistaken payment or refund record is voided.job, payment (with voidedAt and voidReason set)
customer.createdWhen a new customer is created in the dashboard, at check-in or through the API. Not sent for CSV imports.customer

Verifying the signature

Every request's islemde-signature header looks like t=1790000000,v1=5f2b…. v1 is the HMAC-SHA256 digest (lowercase hex) of t + "." + body using your signing secret. Compare it with your own in constant time, and reject t values older than 5 minutes. Use the body exactly as received, without parsing it: a single space breaks the signature.

// Node 18+ ve Express: npm install express
import crypto from "node:crypto";
import express from "express";

const app = express();
const secret = process.env.ISLEMDE_WEBHOOK_SECRET; // whsec_ ile başlar

// İmza ham gövde üzerinden hesaplanır: JSON'u ayrıştırmadan önce doğrulayın.
app.post("/islemde-webhook", express.raw({ type: "application/json" }), (req, res) => {
  const parts = (req.get("islemde-signature") ?? "").split(",").map((part) => part.split("="));
  const t = parts.find(([key]) => key === "t")?.[1];
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${t}.${req.body}`)
    .digest("hex");
  // Anahtar yenilendikten sonraki 24 saatte iki v1 gelir: biri tutması yeter.
  const valid = parts.some(
    ([key, value]) =>
      key === "v1" &&
      value.length === expected.length &&
      crypto.timingSafeEqual(Buffer.from(value), Buffer.from(expected)),
  );
  // 5 dakikadan eski imzaları reddedin: yakalanıp yeniden gönderilen istekler geçmez.
  const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;
  if (!valid || !fresh) return res.sendStatus(400);

  const event = JSON.parse(req.body);
  // Aynı olay birden fazla gelebilir: event.id'yi saklayıp tekrarları atlayın.
  res.sendStatus(200); // Önce cevap verin; uzun işleri sonra yapın.
  console.log(event.type, event.data.job?.number);
});

app.listen(3000);

When you rotate the secret, events carry two signatures for 24 hours: t=…,v1=<new>,v1=<old>. Try each v1 value; if one matches, the request is valid. That way you can update your server within that window without downtime.

Retries

Any non-2xx response, a wait over 5 seconds, a connection error or a redirect (3xx) counts as a failure; redirects aren't followed. A failed delivery is retried at these intervals: 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours, 24 hours. After the seventh attempt we give up; you can resend it by hand from the list under Settings → API.

If deliveries to a URL (retries included) fail 20 times in a row and nothing has got through for 24 hours, the URL is turned off and the business owner is notified. Short outages don't turn it off. Fix the URL and turn it back on from the same place; events that happen while it's off aren't sent.

Best practices

  • Verify the signature first, then return 200 right away; do long work in your own queue. Responses that take over 5 seconds are retried.
  • The same event can arrive more than once. Store each event's id and skip the ones you've already handled.
  • Event order isn't guaranteed. For the current state, check the statusChangedAt field or reread the job with GET /api/v1/jobs/{number}.
  • If your URL was down for a long time, collect the changes in between with changedSince.
  • Keep the signing secret as safe as your API key; rotate it if you think it has leaked.

Need another endpoint? Write to us from Support in the dashboard or at destek@islemde.app. Changes are announced on the What's new page.