Service Desk Partner API

Headless Mode

Turn off every message DevStride sends your customers and receive each one as a signed customer.message webhook instead, ready to send from your own domain and tool: the six message kinds, their fields, sample events, and what still happens.

In headless mode DevStride sends nothing to your customers. Every email it would have sent a requester (a staff reply, an acknowledgement, a status update, a sign-in link) is delivered to your webhook endpoint instead, as one customer.message event. Your own tool sends it, from your own domain, in your own branding.

Everything else keeps working: inbound email still creates tickets, the API still works, SLA clocks keep running, and staff see and do exactly what they did before.

Before you turn it on

  1. Create a webhook endpoint subscribed to customer.message, and make sure it is enabled. Headless mode refuses to turn on without one, so customer messages can never go nowhere.
  2. Verify signatures and deduplicate on the event id, as for every webhook: see Verifying Signatures and Delivery, Retries & Ordering.
  3. Turn on Headless mode in Settings → Service Desk → Customer communication, or with the settings API below. See Headless Mode for what administrators see.

While headless mode is on, DevStride also refuses to delete, disable or unsubscribe the last enabled endpoint receiving customer.message. Subscribe a second endpoint first, or turn headless mode off.

The customer.message event

Each event is one message for one customer. subject, text and html are already written and ready to send. They contain no DevStride logo, image, button, footer or link. html is plain paragraphs and lists, plus, for a reply, the reply's own formatting. text is the same message with the tags removed.

FieldTypeDescription
data.kindstringWhat the message is: one of the six kinds below.
data.messageobjectThe facts specific to that kind (below). {} when there are none.
data.itemNumberstring or nullThe ticket it is about, for example I4821. null for login_link and portal_redirect, which are about no single ticket.
data.itemTitlestring or nullThat ticket's title. null when itemNumber is.
data.requesterobjectWho to send it to: { "id", "email", "name" }. email is lower case; name can be null. Read when the message is sent, so it is always the requester's current address.
data.companyIdstring or nullThe requester's company, or null.
data.subjectstringThe subject line to use.
data.textstringThe message as plain text.
data.htmlstringThe message as HTML.

Send it to data.requester.email. Nothing else in the event needs to reach the customer.

The six kinds

kindSent whendata.messageWhat you do
replyA customer-visible reply is added to a ticket by staff in DevStride, or by an automation (for example the auto-close notice).commentId, attachments (an array of file URLs the reply links to or embeds).Send it. Report the outcome with a delivery receipt for commentId if you like.
request_receivedA new ticket is created from a customer's email.{}Send the acknowledgement, or drop it if your tool sends its own.
request_confirmationA customer submitted a request DevStride holds until they confirm their email address.verificationTokenAsk the customer to confirm their address in your tool, then call the confirm API with the token.
status_changeA ticket moves to a status with a different customer-facing label. Not sent when Email customers on status change is off.fromLabel (or null), toLabelSend it, or update your own portal instead.
login_linkA customer asked to sign in to the DevStride customer portal.magicLinkUrl, otpCodeSend it only if you still offer the DevStride portal.
portal_redirectA customer asked to sign in at the wrong company's portal and is pointed to their own.companyName, portalLoginUrlSend it only if you still offer the DevStride portal.

The status labels are open, in_progress, awaiting_your_reply, solved and closed. The rendered text uses their display form: Open, In progress, Awaiting your reply, Solved, Closed.

login_link and portal_redirect carry DevStride portal addresses, and are sent only while the customer portal is open. If you close the portal (Customer portal off), they stop.

Origin, and your own replies

origin follows the webhook rules:

  • a reply reads member, with the username of the member who wrote it in actorId (for an automation's reply, the automation's creator);
  • every other kind reads system, with actorId null.

A reply you post through the API never comes back as a customer.message. A staff reply you record is one your tool already delivered, so DevStride does not hand it back to you to send again. The comment.created event for it still fires, with origin.kind api.

Samples

A staff reply with an attachment:

{
  "id": "4b8e2f1c-7d3a-5c9e-b1f0-2a6d8e4c9b17",
  "type": "customer.message",
  "apiVersion": "2026-10-16",
  "occurredAt": "2026-10-24T15:12:40.218Z",
  "organizationId": "8f14e45f-ceea-4e7a-9b1d-3c5a2f6e9d10",
  "origin": {
    "kind": "member",
    "actorId": "jordan.lee",
    "correlationId": "e3c1a7b9-2f4d-4e6a-9b8c-1d5f7a3e2c60"
  },
  "data": {
    "kind": "reply",
    "message": {
      "commentId": "c2f7a9d1-6e3b-4a8c-b5d0-9e1f4a7c3b28",
      "attachments": [
        "https://media.example.com/attachments/printer-settings.png"
      ]
    },
    "itemNumber": "I4821",
    "itemTitle": "Printer on floor 3 is offline",
    "requester": {
      "id": "7a3d9c2e-1b5f-4e8a-a6c4-3f9e2d7b1c05",
      "email": "sam.rivera@example.com",
      "name": "Sam Rivera"
    },
    "companyId": "5e9b1d3f-8c2a-4f6e-b7d1-0a4c8e2f6b93",
    "subject": "Re: Printer on floor 3 is offline",
    "text": "Thanks Sam, it is back online now.\n\nThe settings we changed are attached.",
    "html": "<p>Thanks Sam, it is back online now.</p><p>The settings we changed are <a href=\"https://media.example.com/attachments/printer-settings.png\">attached</a>.</p>"
  }
}

The other kinds have the same envelope, with origin.kind system and actorId null. Their data:

{
  "kind": "request_received",
  "message": {},
  "itemNumber": "I4822",
  "itemTitle": "VPN drops every hour",
  "requester": { "id": "7a3d9c2e-1b5f-4e8a-a6c4-3f9e2d7b1c05", "email": "sam.rivera@example.com", "name": "Sam Rivera" },
  "companyId": "5e9b1d3f-8c2a-4f6e-b7d1-0a4c8e2f6b93",
  "subject": "We received your request “VPN drops every hour”",
  "text": "We received your request “VPN drops every hour”.\n\nWe will reply as soon as we can.",
  "html": "<p>We received your request “VPN drops every hour”.</p><p>We will reply as soon as we can.</p>"
}
{
  "kind": "request_confirmation",
  "message": { "verificationToken": "EXAMPLE-TOKEN-not-a-real-value" },
  "itemNumber": "I4823",
  "itemTitle": "New laptop for a starter",
  "requester": { "id": "1c6e8a4b-3d9f-4b2e-8a7c-5f0d2b9e6a41", "email": "alex.kim@example.com", "name": null },
  "companyId": null,
  "subject": "Confirm your email to submit “New laptop for a starter”",
  "text": "Please confirm your email address to submit your request “New laptop for a starter”.\n\nIf you did not make this request, you can ignore this message.",
  "html": "<p>Please confirm your email address to submit your request “New laptop for a starter”.</p><p>If you did not make this request, you can ignore this message.</p>"
}
{
  "kind": "status_change",
  "message": { "fromLabel": "in_progress", "toLabel": "solved" },
  "itemNumber": "I4821",
  "itemTitle": "Printer on floor 3 is offline",
  "requester": { "id": "7a3d9c2e-1b5f-4e8a-a6c4-3f9e2d7b1c05", "email": "sam.rivera@example.com", "name": "Sam Rivera" },
  "companyId": "5e9b1d3f-8c2a-4f6e-b7d1-0a4c8e2f6b93",
  "subject": "Your request \"Printer on floor 3 is offline\" is now Solved",
  "text": "Your request “Printer on floor 3 is offline” is now Solved.",
  "html": "<p>Your request “Printer on floor 3 is offline” is now Solved.</p>"
}
{
  "kind": "login_link",
  "message": { "magicLinkUrl": "https://app.devstride.com/portal/example-co/login/verify?token=EXAMPLE", "otpCode": "123456" },
  "itemNumber": null,
  "itemTitle": null,
  "requester": { "id": "7a3d9c2e-1b5f-4e8a-a6c4-3f9e2d7b1c05", "email": "sam.rivera@example.com", "name": "Sam Rivera" },
  "companyId": "5e9b1d3f-8c2a-4f6e-b7d1-0a4c8e2f6b93",
  "subject": "Sign in to Example Co support",
  "text": "Use this link to sign in to Example Co support: https://app.devstride.com/portal/example-co/login/verify?token=EXAMPLE\n\nOr enter this code: 123456\n\nIf you did not ask to sign in, you can ignore this message.",
  "html": "<p>Use this link to sign in to Example Co support: https://app.devstride.com/portal/example-co/login/verify?token=EXAMPLE</p><p>Or enter this code: 123456</p><p>If you did not ask to sign in, you can ignore this message.</p>"
}
{
  "kind": "portal_redirect",
  "message": { "companyName": "Northwind", "portalLoginUrl": "https://app.devstride.com/portal/northwind/login" },
  "itemNumber": null,
  "itemTitle": null,
  "requester": { "id": "7a3d9c2e-1b5f-4e8a-a6c4-3f9e2d7b1c05", "email": "sam.rivera@example.com", "name": "Sam Rivera" },
  "companyId": "5e9b1d3f-8c2a-4f6e-b7d1-0a4c8e2f6b93",
  "subject": "Your Example Co support portal",
  "text": "Your requests with Example Co are in the Northwind support portal.\n\nSign in there: https://app.devstride.com/portal/northwind/login",
  "html": "<p>Your requests with Example Co are in the Northwind support portal.</p><p>Sign in there: https://app.devstride.com/portal/northwind/login</p>"
}

The portal addresses in the last two samples are illustrations; use the URLs exactly as they arrive.

Attachments

A reply's files stay where DevStride stores them. data.message.attachments lists the public URLs the reply's html already links to or embeds, the same ones DevStride's own emails use. Link to them, or download and attach them in your tool. Files from your customers go in the other direction through the upload operations.

Who gets nothing

No message is sent to a requester who is blocked or erased. Automatic notices (request_received, request_confirmation, status_change) are not sent to a no-reply address. The same rules govern DevStride's own email.

Settings API

The four switches are one call, PATCH /service-desk/settings, with the Manage Service Desk Settings permission. Send only the keys you change.

KeyDefaultMeaning
headlessModefalseDevStride sends nothing to customers and emits customer.message instead. Turning it on is refused (400) unless an enabled endpoint is subscribed to customer.message.
customerPortalEnabledtrueThe customer portal is open. When false, it answers as if it did not exist, no email or message offers a portal link, and login_link and portal_redirect stop.
helpCentreEnabledtrueThe help centre (the portal's knowledge base) is open. When false, its pages answer as if they did not exist.
notifyCustomerOnStatusChangetrueCustomers are told when their ticket's customer-facing status changes, by email or, in headless mode, by a status_change message.
curl -X PATCH "https://api.devstride.com/v1/organizations/$ORG_ID/service-desk/settings" \
  -u "$DEVSTRIDE_API_KEY:$DEVSTRIDE_API_SECRET" \
  -H "Content-Type: application/json" \
  -d '{ "headlessMode": true, "customerPortalEnabled": false, "helpCentreEnabled": false }'

The response is the updated organization. To read the switches at any time, GET /v1/organizations/{organizationId} returns the organization under data, with serviceDeskHeadlessMode, serviceDeskCustomerPortalEnabled, serviceDeskHelpCentreEnabled and serviceDeskNotifyCustomerOnStatusChange and their defaults filled in.

What still happens in headless mode

  • Inbound email to your channel addresses keeps creating tickets and adding replies.
  • The API keeps working: everything in this guide.
  • SLA clocks keep running, and sla.at_risk and sla.breached still fire.
  • Staff keep their notifications, internal notes and every screen they use.
  • The customer portal and help centre stay open unless you switch them off. A headless organization that does not want customers to see DevStride's portal should close both.

Limits today

  • English only. The rendered subject, text and html are written in English. Write your own copy from kind and message if you need another language.
  • Receipts for replies only. A delivery receipt needs a comment, so only reply messages can take one.
  • "Powered by DevStride" stays on the portal. It is on every portal page. If that matters to you, close the portal.
  • Contact email confirmation is not available. Staff cannot send a requester's email confirmation while headless mode is on; DevStride refuses it and says why.