Skip to content
Open the app

Two-way help-desk sync

Most universities already run a support system their teams live in: ServiceNow, Jira Service Management, Zendesk, a counselling case system. First Six is built to route a student's request to that system (through outbound webhooks and email-to-case queues) and then get out of the way. This endpoint is the returning half: your system tells First Six where the case is up to, so the staff inbox and the student's "My requests" reflect reality instead of saying "new" forever.

What this endpoint is for

Status truth, not conversation. Your team acknowledges, works, and resolves the case in your system; this endpoint mirrors those state changes back. Replies to the student happen wherever you normally reply to students.

The safety boundary

An external credential can never:

  • Change a request's priority. Crisis can never be downgraded from outside; the write path does not touch priority at all.
  • Assign or unassign staff. Assignment stays an internal judgement.
  • Read anything. The endpoint returns only the outcome of its own writes. There is no GET, and a write against another institution's request returns the same not_found as a request that does not exist.

Every inbound update lands in the append-only audit trail attributed to your integration's name, so a reviewer can always tell a machine's write from a person's.

Authentication

An institution admin issues the sync secret in the console under Settings → Integrations → Two-way help-desk sync (requires the Manage integrations permission). The secret is shown once and stored as a hash. Send it as a Bearer token:

POST /api/integrations/help-sync
Authorization: Bearer <your sync secret>
Content-Type: application/json

There is deliberately no platform-wide fallback credential: until your admin issues a secret, this surface does not exist for your institution. Revoking it in the console shuts the door immediately.

The request

{
  "institution": "your-slug",
  "actor": "ServiceNow (Student Services)",
  "updates": [
    {
      "help_request_id": "6b1e0c2a-…",
      "action": "acknowledge",
      "external_ref": "SN-CASE-0042"
    },
    {
      "help_request_id": "8c2f1d3b-…",
      "action": "resolve",
      "note": "Student contacted and supported; case closed in ServiceNow."
    }
  ]
}
  • institution: your institution slug (required).
  • actor: how your updates are named in the audit trail and inbox timeline. Set it to something a staff member reading the timeline will recognise. Can be overridden per update.
  • updates: up to 100 per request. Each row succeeds or fails on its own; the batch never aborts halfway.
  • help_request_id: comes from the outbound webhook payload (help_request_id).

Actions

ActionEffectWhen it changes nothing
acknowledgenewack ("your team has seen it")Any other starting status
noteAdds a staff-visible note to the request's timelineNever (but note text is required)
resolveAny open status → resolved, stamps the resolved timeAlready resolved
reopenresolvedin_progress, clears the resolved timeNot currently resolved

Any update may also carry external_ref (your own case id, up to 200 characters). It is stamped on the request, shown in the timeline, and echoed in every subsequent webhook payload for that request, so you never need a mapping table. Actions that change nothing still return ok: true with changed: false, so retries are harmless.

The response

{
  "results": [
    { "help_request_id": "6b1e0c2a-…", "ok": true, "status": "ack", "changed": true, "external_ref": "SN-CASE-0042" },
    { "help_request_id": "8c2f1d3b-…", "ok": false, "error": "not_found" }
  ]
}

Row-level error values: not_found (no such request in your institution), bad_action, bad_help_request_id, note_required, update_failed (transient; retry). Request-level errors use HTTP status codes: 401 for credential problems (the body says whether the secret was missing, wrong, revoked, or never issued), 400/413 for structural problems, 429 when rate-limited.

Avoiding echo loops

Every change this endpoint makes flows back out through the outbound webhooks, exactly like a change made in the console. Those events carry "origin": "integration"; everything else carries "origin": "platform". If your consumer writes back through this endpoint, drop origin: "integration" events. They are your own updates returning.

Putting the loop together

  1. Subscribe to the outbound webhooks

    Configure an endpoint for help_request.created and help_request.crisis under Settings → Integrations → Outbound webhooks, and open a case in your system when one arrives. See webhooks.

  2. Stamp your case id

    When you open the case, send one acknowledge update with external_ref set to your ticket number. The inbox timeline shows the linkage and every later webhook payload carries it.

  3. Mirror the close

    When your team resolves the case, send resolve (a closing note is worth including: it lands in the timeline for the staff who use the First Six inbox). If the case reopens, send reopen.

  4. Test with a real round trip

    Raise a test help request as a preview student, watch it arrive in your system, resolve it there, and confirm the First Six inbox and the student's "My requests" show resolved.

Troubleshooting

401 with "No help-desk sync secret has been issued for this institution."

Nobody has minted the credential yet. An institution admin issues it in the console under Settings → Integrations → Two-way help-desk sync. There is no platform default secret for this surface, deliberately.

401 with "This institution's help-desk sync secret has been revoked."

An admin revoked the credential, which shuts inbound sync until a new one is issued. Rotating in the console produces a fresh secret; the old one stops working the moment it is replaced.

Every update returns "not_found" but the ids look right

Two common causes. The id being sent is your own ticket number rather than the First Six help_request_id from the webhook payload; or the institution slug in the body does not match the institution the secret was issued for. A request belonging to a different institution returns the same not_found as a request that does not exist, by design.

Your system keeps re-processing its own updates

Your webhook consumer is reacting to events your sync writes caused. Drop events with "origin": "integration" before they reach your case logic; they are echoes, not news.

A resolve worked but the student still sees the request as open

Check the response row: ok: true with changed: false means the request was already in that state, and status tells you what the platform currently holds. If a staff member reopened it in the console after your resolve, the console wins; subscribe to help_request.updated to hear about it.

Next steps

Was this helpful?
Need more help?

The fastest answer is usually one question away.

Contact us
Edit this page on GitHub