Docs
API 1.39
Status
Guides

Hosted resolve pages

Let your own end users finish a parked job on a page with no account: type a one-time code, apply a fix, or take over.

A hosted resolve page (https://app.kaab.ist/r/<token>) lets a person without a Kaabist account finish one parked job: type the code their bank has texted them, apply a suggested fix, or deal with the page themselves. You send them the link; the page names your organisation, says in plain words what is needed, and does one thing for one job.

#When to use it

Use a hosted page when the value or the decision belongs to your end user, not to you: the one-time code arrives on their phone, or the account the job signs in to is theirs. The code then goes from their phone to the job without passing through your servers.

When you, your backend or your operators can supply the value, answer through the API instead (POST /v1/jobs/{id}/input, or an SDK's onInput); see Authenticated flows. When a stopped job needs a judgement about the program itself (skipping a step, retrying from a checkpoint), handle it in the panel: a hosted page never offers those.

#Collecting a value

Declare the input as hosted in the program:

{ "await_input": { "name": "otp", "prompt": "The code we sent you", "pattern": "^[0-9]{6}$",
  "collect": "hosted", "hosted": { "notify": "none" } } }

When the job parks there, its envelope carries the page's address from the first moment:

"input_request": { "name": "otp", "collect": "hosted", "resolve_url": "https://app.kaab.ist/r/…", … }

Then get the link to the person. With "notify": "none" you deliver it yourself, by e-mail, SMS or in your own app; the job.awaiting_input webhook is the moment to do it, and input_request.resolve_url on the job it points at is the link. The program language also accepts "notify": "email" with a "to" address (a template such as "{{inputs.email}}"); this guide delivers the link itself, which works whatever notify says.

Lint warns about a hosted input with no hosted.notify at all (W007): it is a reminder that somebody has to tell the person the page exists, or the job waits until its deadline.

The person opens the link, types the value, and the job continues. The value is checked against the step's type and pattern before it is accepted, on the page and again by the API.

#Sharing a stopped job

A job that stopped for something unexpected (needs_attention) can be shared from its page in the panel: Share resolve link. The dialog explains that anyone with the link can act on this one job until it finishes or the park expires, and has one option, Let them abort the job, off by default. The job's attention.resolve_url then holds the link.

The shared page shows the screenshot the job stopped on, a plain-sentence description of what is in the way, the platform's suggested fixes (applied for this one job only), and Open the page: a takeover of the remote browser with no address bar, no tabs and no session export.

#What the person sees

The page carries no Kaabist navigation and no sign-in. At the top, in small grey text, is your organisation's name; under it the heading We need one more thing from you for an input, or We need your help to continue for a stopped job. The footer reads "Secure page · the link works for one task and expires", and the line under the card says when this link expires.

On a phone the page is a single column with a small margin on each side. For an input, the screenshot of the page the job stopped on (when there is one) fills the width, followed by one large field labelled with your prompt and a full-width Send button. The field is marked as a one-time-code field, so phones offer a code that has arrived by SMS; a number input opens the numeric keypad, and a secret input is masked. A value that does not match the pattern is refused under the field before anything is sent. For a stopped job, each suggested fix is a short sentence with an Apply this fix button below it, followed by Or do it yourself and an Open the page button.

On a desktop the same content sits in a centred card at most 768 pixels wide, with more padding, and each suggested fix has its Apply this fix button on the same line as its sentence. Opening the page for a takeover widens the card to the full window so the remote browser has room.

When the shared link allows it, a red Stop this task button sits at the bottom of the card and asks for confirmation ("It will not continue, and this link stops working").

After an answer the card is replaced by its outcome: Thank you, it is on its way with a spinner while the job runs (the page checks again every few seconds), then All done. A link past its deadline shows This link has expired, a withdrawn one or one whose job was canceled This link is no longer active, and an address that is incomplete or unknown This link does not work.

One jobA link opens exactly one job, and acts only while that job waits.
ExpiryIt expires with the job's park: an input's expires, or the attention's deadline (a week).
RevocationIt stops working when the job finishes or is canceled.
No accountNothing about your organisation beyond its name is shown; no cookie is set.
Rate30 requests a minute per link.
AbortOnly on a link you shared with Let them abort the job ticked; never on an input's link.
Permanent changesNever. A fix applied from the page affects this job only; making it permanent is an approval in your review queue.

Pages are not indexed by search engines, send no referrer and are never cached. Every action taken on one is recorded on the job.

#Worked example

Take a customer portal at https://portal.example.com that signs in with an e-mail address and a password, then asks for a six-digit code. This program asks for the code on a hosted page:

TypeScript
const definition = {
  kaab: "1",
  name,
  inputs: { email: "string", password: "secret" },
  options: { max_tier: "browser" },
  steps: [
    { goto: `${site}/login`, expect: { visible: "input[name=email]" } },
    { fill: "input[name=email]", value: "{{inputs.email}}" },
    { fill: "input[name=password]", value: "{{inputs.password}}" },
    { click: "form button[type=submit]", expect: { visible: "input[name=code]" } },
    {
      await_input: {
        name: "otp",
        prompt: "The six-digit code we sent you",
        pattern: "^[0-9]{6}$",
        collect: "hosted", // a page at input_request.resolve_url collects it
        hosted: { notify: "none" }, // or { notify: "email", to: "{{inputs.email}}" }
      },
    },
    { fill: "input[name=code]", value: "{{vars.otp}}" },
    { click: "form button[type=submit]", expect: { url: "**/dashboard" } },
    { extract: { heading: "h1" } },
  ],
};
await kaab.programs.create({ name, definition, publish: true });

const job = await kaab.jobs.create({
  program: name,
  inputs: { email: "[email protected]", password: "secret" },
});
// Without an onInput handler, wait returns as soon as the job parks.
const parked = await kaab.jobs.wait(job.id, { timeoutMs: 120_000 });
console.log(`${parked.id} is ${parked.status}: answer at ${parked.input_request?.resolve_url}`);
  1. Create the job with inputs: { "email": "[email protected]", "password": "<the account password>" }.
  2. It signs in and parks awaiting_input. input_request.resolve_url is set; the snippet prints it.
  3. Open the link in a private window: the page names your organisation and asks for "The six-digit code we sent you". Type the code the site sent, for example 482913, and press Send.
  4. The page says Thank you, it is on its way, then All done; the job is succeeded, with the dashboard's heading in data.heading.

#Pitfalls

  • The link is a credential. Whoever holds it can act on that job. Send it only to the person meant to answer, over a channel you trust, and never log it.
  • Answering twice. Once a value is accepted, the page moves on; a second submission is refused because the job is no longer waiting.
  • Deadlines. An input's expires is at most 7 days. Choose one that matches how quickly your users react; the browser itself is kept open only for the step's ttl (at most 15 minutes), and a later answer resumes from the last checkpoint.
  • Expecting the page to change the program. Applying a fix from a hosted page never publishes anything. Review the proposal in the panel if it should apply to every job.

#Reference

Was this page helpful?