import { Aside, Code } from '@astrojs/starlight/components';
import { apiOrigin, supportEmail } from '../../../lib/docs-env';

Checktiv replaces identity verification from Autohost. This guide is for product managers and developers who built on the Autohost SDK (`AutohostSDK`), the hosted Guest Portal, or the Autohost Data API (`data.autohost.ai`). It tells you what changes, what stays the same, and how to move each part of your integration.

<Aside type="tip" title="Let an AI assistant write your migration plan">
Click **Copy as Markdown** at the top of this page. Paste the result into the AI assistant you already use, for example ChatGPT, Claude, or GitHub Copilot. Then describe your stack and paste the code that calls Autohost.

Before you paste code, remove API keys, SDK keys, webhook secrets, and applicant data. The assistant does not need them.

Then ask for a plan. For example:

> Here is the Checktiv migration guide for Autohost partners. Our backend is [language and framework]. Our front end is [framework]. Below is our code that calls the Autohost Data API and the Autohost SDK. Give me a step-by-step migration plan with the exact code changes for each file. List anything you cannot map.

To let a coding assistant read the Checktiv guides and developer pages while it works, connect it to the [docs MCP server](/developers/mcp-docs-server).

</Aside>

## Which parts apply to you

| You use                                                     | Read                                                                                                                           |
| ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| Only an Autohost connection to Guesty, Hostaway, or Track   | [Integrations](/guides/integrations) first. A Checktiv integration can replace your code.                                      |
| The Autohost SDK in your applicant pages                    | [Step 3](#step-3-start-verifications-on-your-server) and [Step 4](#step-4-replace-the-autohost-sdk-in-your-applicant-pages)    |
| The hosted Guest Portal, as a link or in an iframe          | [Step 3](#step-3-start-verifications-on-your-server) and [Replace an embedded Guest Portal](#replace-an-embedded-guest-portal) |
| The admin components (`ReservationResults`, `SexOffenders`) | [Step 5](#step-5-replace-the-admin-components)                                                                                 |
| The Data API from your server                               | [Step 6](#step-6-change-endpoints-and-fields) and [Step 7](#step-7-rewrite-your-webhook-receiver)                              |
| The reseller APIs, for many host accounts                   | [Partners with many host accounts](#partners-with-many-host-accounts)                                                          |
| You are a product manager                                   | [Overview](#overview), [What changes for your team](#what-changes-for-your-team), and the [checklist](#migration-checklist)    |

## Overview

### Timeline

The Autohost SDK, the hosted Guest Portal, and the Autohost Data API reach end of life before October 2027. Plan to finish your migration by April 2027.

After the end-of-life date:

- The Autohost API hosts are turned off. Every call to the Autohost Data API and the Autohost SDK fails.
- Guest Portal links stop working, including links you already sent to applicants.
- Autohost sends no more webhooks.

Until that date, your Autohost integration keeps working. You can move to Checktiv at any time before it, and you can run both at the same time while you move.

### What happens to your Autohost data

We copy your Autohost reservations and part of their verification data, including ID images, into your Checktiv organization. Each copied reservation becomes a verification with the decision Autohost recorded. The `external_id` of its applicant is the Autohost reservation id. See [Link copied reservations to your bookings](#link-copied-reservations-to-your-bookings).

We do not copy verification reports or signed e-signature documents (PDFs). Download each report and signed document you must keep before the end-of-life date.

We copy your data into the US region. If your applicants' data must stay in the EU, create your organization in the EU region. Then email support@autohost.ai before we copy your data, and name that organization.

### What you get

Checktiv covers most of the identity verification and screening checks that the Autohost SDK and Data API offered. The table in [Map Autohost checks to Checktiv checks](#map-autohost-checks-to-checktiv-checks) shows the Checktiv check to use in place of each one. You also get:

- **US or EU data residency.** You pick the region when you sign up. Your applicants' data stays in that region.
- **A test mode with predictable results.** A test-mode key creates free, synthetic verifications. You choose whether each one passes or goes to review.
- **Signed webhooks.** Every delivery carries an HMAC signature and a stable event id. You can replay a delivery from the console.
- **A workspace reviewer.** Your staff approve or decline a verification inside your own admin dashboard. Each token carries only the capabilities you grant.
- **Workflow templates.** You configure the checks once, in the console or over the API, and reuse them by id.
- **Types, React, and cross-device capture.** The npm package ships TypeScript types and a React wrapper. An applicant on a desktop can move to a phone for document capture.
- **Ready-made integrations** for Guesty, Hostaway, and Track. See [Integrations](/guides/integrations).

### What is not part of Checktiv

Checktiv is an identity verification and screening platform. These Autohost features have no Checktiv equivalent:

| Autohost feature                                                                                                                   | What to do                                                                                                                                   |
| ---------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| Security deposits, payment validation, damage waivers (`GET /reservations/{id}/payments`, `POST /reservations/{id}/payment-event`) | Keep these in your own checkout. Checktiv does not take payments from applicants.                                                            |
| Guest messaging (Stargate, `GET /reservations/{id}/stargate`, `stargate_webhook_url`)                                              | Send these messages from your own system. Checktiv can email the verification link: set `send_invite: true`.                                 |
| Business rules that read the reservation details                                                                                   | Keep them on your server. Choose the workflow template before you create the verification. Screening rules cannot read your booking details. |
| The details read from the ID document (`identification`)                                                                           | A verification does not return them over the API. To read the fields from ID images outside a verification, call `POST /v1/id-verification`. Your reviewers see the ID check in the workspace reviewer and the console. |
| Loyalty segments (`/loyalty/*`, `loyalty_segments`)                                                                                | No equivalent. Remove these calls.                                                                                                           |
| Tenant eviction search (`/tenant-evictions/*`)                                                                                     | No equivalent. Remove these calls.                                                                                                           |
| Your own blocklist, social verification, authority reporting                                                                       | No built-in equivalent. Keep this logic in your own system. To act on a list of known emails, add a screening rule on `applicant.email` with the `in` operator. |
| Standalone lookups (`/lookup/*`, `/validate/*`, `/predict/*`, `/fraud/analyze`, `/global-watchlist/*`, `/background-check/*`)      | No standalone equivalent. Run screening inside a verification. See [Map Autohost checks](#map-autohost-checks-to-checktiv-checks).           |

## What changes for your team

Some of your integration stays the same:

- **Your decision logic.** Your code still decides which bookings need a verification. Checktiv does not need your listings.
- **Your tiers.** If riskier bookings get more checks, keep that logic on your server. Create one workflow template per tier, then send the id of the template you chose.
- **Your state management.** You still store the verification id next to your booking, and you still update the booking when the result arrives.

Plan for this new work:

- **A server route that starts the verification.** The Autohost SDK started from a reservation id in the browser. Checktiv starts from your server, which returns a token to the browser. This route must check that the caller is the applicant for that booking.
- **The template decides the steps.** The workflow template decides the steps and their order. The SDK shows one step at a time. After each step, your page reloads or mounts the SDK again to show the next one. A progress bar you built around your own pages must change.
- **Workflow templates.** You create one template for each tier. A document the applicant signs belongs to a template, so different agreement text means a different template.
- **Booking rules stay on your server.** Screening rules in a template read the applicant, the check results, and the fraud signals. They cannot read the dates, the price, or the source of a booking.
- **Translations.** The Checktiv SDK does not translate its screens for you. You pass your own translated text, and any text you leave out shows in English. See [Translations](#translations).
- **A fraud-signal disclosure.** Fraud signals start only after the applicant agrees to the disclosure you show.
- **A new webhook receiver.** It verifies a signature, reads one event type field, and fetches the verification.
- **A server route for the admin reviewer.** It mints a short-lived token pair for one staff member and one verification.

## Migration checklist

1. [Find every place that uses Autohost](#step-1-find-every-place-that-uses-autohost).
2. [Set up your Checktiv organization](#step-2-set-up-your-checktiv-organization): keys, workflow templates, and a webhook subscription.
3. [Start verifications on your server](#step-3-start-verifications-on-your-server).
4. [Replace the Autohost SDK in your applicant pages](#step-4-replace-the-autohost-sdk-in-your-applicant-pages).
5. [Replace the admin components](#step-5-replace-the-admin-components).
6. [Change endpoints and fields](#step-6-change-endpoints-and-fields).
7. [Rewrite your webhook receiver](#step-7-rewrite-your-webhook-receiver).
8. [Test each result in test mode](#step-8-test).
9. [Move your bookings in stages, then remove Autohost](#step-9-move-in-stages-then-remove-autohost).

## Concepts that changed names

| Autohost                                                   | Checktiv                                                                                                                                                |
| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Reservation (the container for a verification request)     | **Session.** The API resource is `/v1/sessions` and the object is `verification_session`.                                                          |
| Guest (`guest` on the reservation)                         | **Applicant.** Send it inline as `applicant` on the create. The inline `applicant` can complete the identity information step; an `applicant_id` cannot.  |
| Listing (`listing_id`, `/listings`)                        | **Not needed.** Send the transaction details in `metadata`. See [Map the create fields](#map-the-create-fields).                                        |
| Guest Portal screens, risk colors, Screening Assistant     | **Workflow template.** It lists the checks and their order. Its screening rules can send a verification to review.                                      |
| Guest Portal URL (`guest_portal_url`)                      | **Verification link** (`applicant_url`). It opens the hosted journey. It cannot be shown in an iframe.                                                  |
| Custom Guest Portal domain                                 | A [custom domain](/guides/organizations/custom-domains) for the verification link.                                                                      |
| Host decision (`supervised`)                               | **Reviewer decision.** `outcome` is `approved` or `declined`, and `decision_source` is `reviewer`.                                                      |
| API key (`x-api-key` header)                               | **Secret key** (`ah_sk_...`) in the `Authorization: Bearer` header.                                                                                     |
| SDK key (used on your server to mint the admin `apiToken`) | **Secret key.** It mints the workspace token for the admin reviewer.                                                                                    |
| No key in the applicant pages                              | **Publishable key** (`ah_pk_...`). It is new, and it is safe to put in a page.                                                                          |
| `sandbox: true` and the Autohost test hosts                | **Test mode.** Use a test-mode key (`ah_sk_us_test_...`) against the same API host.                                                                     |
| Reseller account with child host accounts                  | **One organization**, with each host's verifications marked by `tenant_ref`. See [Partners with many host accounts](#partners-with-many-host-accounts). |
| Autohost MCP server                                        | [Connect an AI assistant](/guides/integrations/connect-ai-assistants) to your Checktiv organization.                                                    |

## Step 1: Find every place that uses Autohost

Search your code and your configuration for `autohost.ai`, `autohost.ca`, `verifyhub.org`, `gueststay.in`, `AutohostSDK`, `x-api-key`, and `guest_portal_url`. Then check these places, which a code search does not find:

- Email, SMS, and booking-confirmation templates that contain a Guest Portal link.
- Guest Portal links that your database stores for future bookings. They stop working at the end-of-life date.
- Mobile apps that open a Guest Portal link.
- Scheduled jobs that read Autohost status.
- Your Content Security Policy and other allowlists.

Give this list to your AI assistant with your code.

## Step 2: Set up your Checktiv organization

<Aside type="caution" title="Start in test mode">
  A key and a webhook subscription take the mode that is selected in the console header. Select
  test mode before you create them. Test mode is free and uses synthetic data. Create live-mode keys
  the same way when your tests pass.
</Aside>

1. In the console, open **Developers → API keys** and click **Create Key**. Select **Secret key** and give it these scopes:

   | Scope                      | Why you need it                                                                       |
   | -------------------------- | ------------------------------------------------------------------------------------- |
   | `sessions:write`           | Create, cancel, and resend verifications, and add notes                               |
   | `applicants:write`         | Send applicant details inline on the create                                           |
   | `sessions:read`            | Read verifications and their results                                                  |
   | `sessions:read_report`     | Download the PDF report and the evidence images                                       |
   | `sessions:workspace_token` | Mint tokens for the workspace reviewer in your admin dashboard                        |
   | `applicants:read`          | Read applicant records, and link copied reservations to your bookings                 |
   | `sessions:browser_token`   | Only if a template has a `custom_form` step, or you translate the signing or credit history screens. See [Translations](#translations). |
   | `idv:write`                | Only if you call `POST /v1/id-verification`                                           |

   See [Choosing scopes for your key](/developers/choosing-scopes).

2. Store the secret key where your server keeps the Autohost API key, for example a secrets manager. Never put it in a page.

3. Click **Create Key** again and select **Publishable key**. Add each origin that loads the SDK. The SDK does not render on an origin that the key does not list.

4. In **Workflows**, create one workflow template for each tier. See [Create a verification workflow template](/guides/workflows/workflow-templates-create).

5. Open **Developers → Webhooks** and click **New subscription**. You need the Owner or Admin role. Select these event types: `kyc.session.completed`, `kyc.session.manual_review_requested`, `kyc.session.cancelled`, `kyc.session.expired`, and, if a template has a signing step, `kyc.session.document_signed` and `kyc.session.document_declined`. A subscription receives events for one mode only, so create one for test mode now and one for live mode later.

6. If you show the admin reviewer, open **Developers → Workspace origin** and enter the `https` origin of your admin dashboard. It takes one origin.

### Partners with many host accounts

If you used the Autohost reseller APIs, we copy your data into one Checktiv organization. Each Autohost host account becomes a partition of that organization, marked by `tenant_ref`. The value is the Autohost user id of that host. If some of your hosts' data must stay in the EU, email support@autohost.ai before we copy your data.

- **Keys and webhooks** belong to the organization, one set for each mode. One secret key and one webhook subscription can serve all your hosts. Each key has its own rate limit, so a batch for one host spends the budget of every host that shares the key.
- **New verifications:** send the host's id as `tenant_ref` on the create. For a host from Autohost, send its Autohost user id. For a new host, send a stable id from your own system. Do not put personal data in it.
- **The workspace reviewer:** mint each workspace token with the host's `tenant_ref`. The reviewer then refuses a verification that belongs to another host.
- **The console** shows the verifications of all your hosts. There is no console login limited to one host, so a host's staff review in your admin dashboard, through the workspace reviewer.
- **Workflow templates** belong to the organization. If hosts need different checks or agreement text, create a template for each and name it after the host.
- **Routing results:** the webhook body does not carry `tenant_ref`. Read it from `GET /v1/sessions/{id}`.
- **Listing by host:** `GET /v1/sessions` does not filter by `tenant_ref`. Keep the host for each verification in your own database.

`POST /accounts/register`, `PUT /accounts/{id}`, `GET /accounts/children`, and `POST /accounts/signup` have no equivalent. To stop a host, stop creating verifications with its `tenant_ref` in your own code. If you are not a reseller, do not send `tenant_ref`.

## Step 3: Start verifications on your server

This step moves the start of each verification from the browser to your server. The Autohost SDK took a reservation id in the browser, and the reservation id was the only credential. The Checktiv SDK takes a client token that your server gets when it creates the verification. Your secret key stays on your server.

Before, with Autohost (browser):

```js
const client = await AutohostSDK.init({ reservationId: RESERVATION_ID });
```

After, with Checktiv (your server):

```js
// POST /api/verifications (your backend). Never call the Checktiv API from a browser.
import { regionToApiBase } from '@checktiv/sdk-web';

app.post('/api/verifications', requireApplicantForBooking, async (req, res) => {
  const booking = req.booking; // set by your own access check

  // Reuse the verification you already created for this booking.
  // When you cancel a verification, clear the stored token first.
  if (booking.checktivClientToken) {
    res.json({ clientToken: booking.checktivClientToken });
    return;
  }

  const response = await fetch(`${regionToApiBase('us')}/v1/sessions`, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.CHECKTIV_SECRET_KEY}`, // ah_sk_*
      'Content-Type': 'application/json',
      'Idempotency-Key': `verification-${booking.id}-${booking.verificationAttempt}`, // a retry returns the same verification
    },
    body: JSON.stringify({
      workflow_template_id: templateIdForTier(booking), // your own logic, for example one template per tier
      applicant: {
        given_names: booking.guestGivenNames, // an array, for example ["Mary", "Ann"]
        family_name: booking.guestFamilyName,
        email: booking.guestEmail,
        phone: booking.guestPhone,
        date_of_birth: booking.guestBirthDate, // YYYY-MM-DD
        booking_name: booking.nameOnBooking, // compared against the ID when Name match is on
        external_id: booking.id, // your own id, returned when you read the applicant
      },
      metadata: {
        start_datetime: booking.checkIn, // ISO 8601 with an offset
        end_datetime: booking.checkOut,
        amount: booking.totalInMinorUnits, // 125000 means 1,250.00 in usd
        currency: booking.currency,
        location: booking.propertyAddress,
        confirmation_code: booking.confirmationCode, // your own key
      },
    }),
  });
  if (!response.ok) {
    logCreateError(booking, response.status, await response.json()); // keep the error on your server
    res.status(502).json({ error: 'verification_unavailable' });
    return;
  }

  const { data } = await response.json();
  await saveVerification(booking, {
    verificationId: data.id, // the id that webhooks refer to
    clientToken: data.client_token, // returned only on this response
    identityDetailsMissing: Boolean(data.warnings), // see the caution below
  });
  res.json({ clientToken: data.client_token, identityDetailsMissing: Boolean(data.warnings) });
});
```

`regionToApiBase('us')` returns `https://api.us.checktiv.com`, and `regionToApiBase('eu')` returns `https://api.eu.checktiv.com`. This sample calls the REST API directly because it needs the verification id. The `sessions.create` helper in the SDK returns only the client token.

Rules for this route:

- **Check the caller.** The client token resumes the applicant's verification. Return it only to the applicant for that booking, for example after you check a signed link or a login.
- **Store the client token.** The create returns `client_token` once, and a read never returns it again. Store it with the booking and return the stored value when the applicant comes back.
- **Send the identity details.** See the caution below.
- **Keep errors on your server.** A Checktiv error can describe your key's region or your request. Log it, and send the browser a short error of your own.
- **Use one region.** An EU organization uses `regionToApiBase('eu')` and EU keys. A key works only in its own region.

<Aside type="caution" title="The SDK does not show the identity information step">
A workflow template can start with a `collect_user_info` step that asks for the applicant's name and, depending on its settings, date of birth, address, email, or phone. The `watchlist`, `background_us_criminal`, `background_global`, `credit_history`, and `esign` checks need that step. The embedded SDK does not show it. If the step is not complete when the journey reaches it, the journey stops with `sdk_load_failed`.

Complete the step in one of two ways:

- **On the create.** Send the name parts on the inline `applicant`, plus `date_of_birth`, `email`, or `phone` when the step asks for them. If the step asks for an address, also send a structured `address`. The step is then complete when the verification starts. An existing `applicant_id` does not complete the step.
- **From your own form.** Collect the details in your page and send them with `collectUserInfo` before you mount. See [Collect user info](/developers/sdks/collect-user-info).

When an inline `applicant` cannot complete the step, the create still succeeds, and `data.warnings` names the missing value with the code `collect_user_info_not_presatisfied`. The sample returns `identityDetailsMissing` so that your page can show its own form first. A create with `applicant_id` returns no warning.

</Aside>

## Step 4: Replace the Autohost SDK in your applicant pages

### Remove the Autohost SDK

1. Delete the Autohost script tag. It looks like one of these:

   ```html
   <script src="https://sdk.autohost.ai/dist/AutohostSDK.v3.bundle.js"></script>
   <script src="https://sdk.autohost.ai/dist/AutohostSDK.v3.11.0.bundle.js"></script>
   ```

2. If you installed the Autohost package with npm, remove it:

   ```bash
   npm uninstall @autohostai/autohost-sdk
   ```

3. Remove every call to `AutohostSDK.init`, `client.component(...)`, `client.verification.save`, `client.reservation.get`, `AutohostSDK.fraudCollector`, and `AutohostSDK.createSecuredToken`.

4. Remove every `autohost.ai` and `autohost.ca` host from your Content Security Policy and other allowlists. Also remove `verifyhub.org` and `gueststay.in` if you embedded the Guest Portal.

Do this one booking at a time. A booking with an Autohost verification in progress keeps the Autohost code until that verification ends. See [Step 9](#step-9-move-in-stages-then-remove-autohost).

### Install the Checktiv SDK

```bash
npm install @checktiv/sdk-web
```

Or load it from the CDN. The script sets `window.Checktiv` and registers every module:

```html
<script src="https://sdk.us.checktiv.com/v1/sdk.js" crossorigin="anonymous"></script>
```

For production, pin a version and add a Subresource Integrity hash. See [Versioning](/developers/sdks/versioning). If your pages send a Content Security Policy or a Permissions-Policy, add the Checktiv origins. See [Security headers](/developers/sdks/security-headers).

### Mount the journey

Before, with Autohost:

```js
const reservation = client.reservation.get();
AutohostSDK.fraudCollector(
  { reservationID: reservation.id, listingID: reservation.lid, userID: reservation.hid },
  { sandbox: false },
);

client.component('IDV', { callbacks: { onIDVComplete: () => goToNextStep() } }).mount('#target');

await client.verification.save({ step: 'Finish', complete: true, data: {} });
```

After, with Checktiv:

```js
import { mount } from '@checktiv/sdk-web';
import '@checktiv/sdk-web/idv'; // identity verification
import '@checktiv/sdk-web/fraud'; // only if your templates use fraud signals
import '@checktiv/sdk-web/esign'; // only if your templates have an esign step
import '@checktiv/sdk-web/credit-history'; // only if your templates have a credit_history step
import '@checktiv/sdk-web/capture-ui/style.css'; // required: styles the capture screens

const handle = mount('#target', {
  publishableKey: 'ah_pk_us_test_...',
  resumeKey: bookingId, // an applicant who comes back resumes at the current step
  fetchToken: () =>
    fetch('/api/verifications', { method: 'POST' })
      .then((res) => res.json())
      .then((body) => body.clientToken),
  onConsent: () => showFraudDisclosure(), // resolve true to start fraud signals, false to decline
  onComplete: () => window.location.reload(), // the applicant submitted a step: reload to show the next one
  onEvent: (event) => {
    if (event.type === 'checktiv.idv.processing' && event.reason === 'no_renderable_step') {
      showSubmittedScreen(); // nothing for the applicant to do on this step; the result arrives by webhook
    }
  },
  onError: (error) => showError(error.code),
});
```

On the npm path, each step type needs its import. A step without its import stops the journey with `sdk_load_failed`. The CDN script registers every module, so it needs no imports.

What changes:

- **The template decides the steps.** You do not mount each step yourself, and you do not call `save` for each step. Each mount shows the applicant's current step.
- **Reload after each step.** `onComplete` fires each time the applicant submits a step. The journey does not move to the next step by itself: reload the page, or mount again, to show it. When there is nothing for the applicant to do, for example after the last step, the mount reports `checktiv.idv.processing` with `reason: "no_renderable_step"`. Show your own "submitted" screen then.
- **No finish call.** Submitting the last step ends the applicant's part, and you send no completion signal. Remove `complete: true` from the `save` call that carried it.
- **Fraud signals need consent.** `onConsent` must resolve `true`. A callback that resolves `false`, resolves nothing, or throws keeps fraud signals off, and the SDK reports no error. With `mount`, you do not call a fraud collector yourself. Autohost recommended calling its collector at least twice.
- **`onComplete` is not a result.** It carries no ID data and no verdict. Get the outcome from the webhook or from `GET /v1/sessions/{id}` on your server.
- **Desktop-to-phone capture.** To offer it, also import `@checktiv/sdk-web/idv/cross-device`, pass `crossDeviceCopy`, and call `handle.openCrossDevice()` from your own button. See [Cross-device handoff](/developers/sdks/cross-device).

For React, use `<ChecktivJourney>`. It takes the same options. See [React](/developers/sdks/react).

### Replace an embedded Guest Portal

If you showed the Guest Portal in an iframe (`verifyhub.org/{id}?embed=1`, or `gueststay.in` in test), mount the SDK in its place, as shown above. The Checktiv verification link cannot be shown in an iframe: the hosted journey refuses to load inside another site's page.

| Autohost Guest Portal embed                                    | Checktiv                                                                                         |
| -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| `<iframe src="https://verifyhub.org/{id}?embed=1">`            | `mount(...)` in your page, or open the verification link in a new tab                            |
| `isMobile=yes`                                                 | Not needed. For a full-screen capture on phones, pass `layout: 'immersive'` to `mount`.          |
| `allow="geolocation; camera; fullscreen;"` on the iframe       | Not needed. The SDK sets the camera permission on its own frame. If your page sends security headers, add the Checktiv origins. |
| `postMessage` event `guestportal-completed`                    | The `no_renderable_step` event above, then the result by webhook                                 |
| `postMessage` event `guestportal-step-completed`               | `onComplete`                                                                                     |
| `is_repeat_guest`, `is_previously_declined_guest` in the event | No browser event. A screening rule on `fraud.repeatApplicant` can send a verification to review. |

### Map the SDK components, options, and callbacks

| Autohost SDK                                                                                   | Checktiv                                                                                                                                          |
| ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `client.component('IDV', ...)`                                                                 | The `id_verification` step. Set **Face match** to **Selfie photo** or **Active liveness** in the workflow template.                               |
| `IDV` option `isIDCollectOnly` (ID Collection)                                                 | The `id_verification` step with **Face match** set to **Selfie photo** or **Active liveness**. Autohost ID Collection also matched a selfie.      |
| `client.component('Selfie', ...)` (a selfie with no ID check)                                  | No selfie-only step. Use the `id_verification` step with **Face match** set to **Selfie photo**.                                                  |
| `IDV` options `allowSelfieRetryInPlace`, `includeWhatToExpect`, `isSinglePage`                 | No equivalent. The journey controls retries and its own screens.                                                                                  |
| `client.component('ElectronicSignature', ...)` and `save` for `UsageAgreement` or `HouseRules` | The `esign` step. See [Documents applicants sign](/guides/workflows/document-signing).                                                            |
| `ElectronicSignature` options `signatureModes`, `showTermsCheckbox`, `emailSignedCopy`         | For `emailSignedCopy`, the `esign` step has **Email a copy to the applicant**, on by default. `signatureModes` and `showTermsCheckbox` have no equivalent: the applicant always signs by typing their name. The template sets the other signing options, such as **Require the applicant to scroll to the end** and **Minimum signer age**. |
| `client.component('PersonalInfo', ...)` and `save({ step: 'PersonalInfo', ... })`              | The `applicant` object on the create, or `collectUserInfo`. See [Step 3](#step-3-start-verifications-on-your-server).                             |
| `save` for `GuestList`, `PurposeOfStay`, or `AboutYou`                                         | A `custom_form` step. `mount` cannot show it: use `init(...)` and `client.mountProvisioned(...)` with `customFormConfig`. See [Custom forms](/developers/sdks/custom-forms) and [Translations](#translations).                                                                          |
| `save` for `BackgroundCheck`                                                                   | A background check in the workflow template. It reads the name and date of birth that complete `collect_user_info`. A US background check also needs the address. |
| `save` for `CreditCheck`                                                                       | A `credit_history` step. The SDK shows its authorization screens. See [Credit history](/developers/sdks/credit-history).                          |
| `save({ step: 'Finish', complete: true })`                                                     | Remove it.                                                                                                                                        |
| `AutohostSDK.fraudCollector(context, options)`                                                 | `import '@checktiv/sdk-web/fraud'` and `onConsent`. See [Fraud consent](/developers/sdks/fraud-consent).                                          |
| `fraudCollector` option `disableGeolocation`                                                   | No equivalent. Checktiv fraud signals do not ask for the device location.                                                                         |
| Remote camera QR code on desktop                                                               | `handle.openCrossDevice()`. See the list above.                                                                                                   |
| `onIDVComplete`                                                                                | `onComplete`. It fires when the applicant submits any step. Reload to show the next step.                                                         |
| `onSkippedIDV`                                                                                 | No browser event. Read the result of the ID check in `check_results` on your server.                                                              |
| `onError`                                                                                      | `onError`. The error carries a `code` from the [SDK error reference](/developers/sdks/error-reference).                                           |
| Other callbacks (`onStartImageUpload`, `onLivenessComplete`, `onSubmit`, ...)                  | `onEvent`. Event types start with `checktiv.`, for example `checktiv.idv.submitted` and `checktiv.esign.submitted`. Ignore types you do not know. |
| `primaryColor`                                                                                 | `theme: { primaryColor }` on `mount`. See [Theming](/developers/sdks/theming).                                                                    |
| `locale`                                                                                       | No `locale` option on `mount`. See [Translations](#translations).                                                                                 |
| `client.reservation.get()`                                                                     | Nothing in the browser. Read the verification on your server with `GET /v1/sessions/{id}`.                                                        |

### Translations

The Autohost SDK translated its screens from a `locale`. The Checktiv SDK does not translate its screen text. You find the applicant's language yourself and pass translated text. In `copy` and `esignCopy`, every key is optional, and a key you leave out shows in English. In `crossDeviceCopy`, `unavailableMessage` is required, and another key you leave out hides that text.

`mount` takes two text options:

- `copy` for the capture screens. See [Advanced capture](/developers/sdks/capture).
- `crossDeviceCopy` for the desktop-to-phone handoff. See [Cross-device handoff](/developers/sdks/cross-device).

With `mount`, the signing and credit history screens show in English, and a `custom_form` step does not show at all: the mount reports `sdk_load_failed` with a message that asks for `customFormConfig`. To translate the signing and custom form screens, or to show a custom form at all, start the journey with `init(...)` and `client.mountProvisioned(...)` and pass `customFormConfig`. On the credit history screens, you can set only the continue, back, and submit button labels. That path also takes `esignCopy`, `customFormCopy`, and `creditCopy`. It uses a browser token from `POST /v1/sessions/{id}/browser_token` in place of the client token. See [Token handoff](/developers/sdks/token-handoff) and [Modules overview](/developers/sdks/modules).

If your applicants read other languages, plan the work to write and maintain these strings.

### Map Autohost checks to Checktiv checks

| Autohost check or screen                                                                                           | Checktiv                                                                    |
| ------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------- |
| Personal info (`PersonalInfo`)                                                                                     | `collect_user_info`                                                         |
| ID verification, ID collection, selfie, liveness (`IDVerification`, `IDCollection`, `Selfie`)                      | `id_verification`                                                           |
| Name match (`name_match`)                                                                                          | **Name match** on the `id_verification` step, with `applicant.booking_name` |
| Sanctions, global watchlist, sex offender search, adverse media                                                    | `watchlist`. `background_global` also searches adverse media. |
| Background check for applicants in the US                                                                          | `background_us_criminal`                                                    |
| Background check for applicants outside the US, and the global check (`/background-check/submit?type=globalcheck`) | `background_global`                                                         |
| Credit check (`CreditCheck`)                                                                                       | `credit_history`, for organizations in the US region                        |
| Rental agreement and house rules (`UsageAgreement`, `HouseRules`)                                                  | `esign`                                                                     |
| Guest list, purpose of stay, about you, file upload, custom screen                                                 | `custom_form`                                                               |
| Fraud detection and device fingerprint                                                                             | The fraud module, with screening rules on its signals                       |

See [Choose the right check types](/guides/workflows/workflow-templates-check-types) for what each check does.

## Step 5: Replace the admin components

The Autohost admin side used `ReservationResults` and `SexOffenders` with a short-lived `apiToken`. Checktiv replaces both with the [workspace reviewer](/developers/sdks/workspace-reviewer). It shows the verification, the evidence, the watchlist matches, and the report.

Before, with Autohost (server, then browser):

```js
// Server: mint an admin token from the SDK key
const { token: apiToken } = await fetch(`https://embed.autohost.ai/api/auth?token=${SDK_KEY}`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ filters: [{ scope: 'read', level: 'reservation', id: reservationId }] }),
}).then((res) => res.json());

// Browser
client.component('ReservationResults', { reservationId, apiToken }).mount('#results');
```

Some integrations sent the same `filters` body to `/sdk/authorization` with the SDK key in an `x-api-key` header. If you minted the admin token in the browser, move that call to your server: a Checktiv secret key must never reach a page.

After, with Checktiv:

1. Set your workspace origin. See [Step 2](#step-2-set-up-your-checktiv-organization).

2. On your server, add a route that mints a token pair with `POST /v1/sessions/{id}/workspace_token`. The route must check that the caller is a staff member who may see this verification. Send:
   - `origin`: your admin origin.
   - `actor`: the staff member, as `{ ext_id, name }`.
   - `capabilities`: what this staff member may do.
   - `tenant_ref`: the host, if you are a reseller.

   The response is `{ data: { framing_token, data_token, expires_at } }`. Return it to the browser as `{ framingToken, dataToken, expiresAt }`. The [workspace reviewer](/developers/sdks/workspace-reviewer#5-minute-quickstart) page has the complete route.

3. In your admin page, mount the reviewer:

   ```js
   import { workspace } from '@checktiv/sdk-web/workspace';

   const reviewer = workspace({
     region: 'us',
     getToken: async ({ sessionId, reason }) => {
       const res = await fetch(`/api/checktiv/workspace-token/${sessionId}`, {
         method: 'POST',
         headers: { 'Content-Type': 'application/json' },
         body: JSON.stringify({ reason }),
       });
       return res.json(); // { framingToken, dataToken, expiresAt }
     },
   });

   const target = document.getElementById('results'); // set an explicit height on this element
   if (target) reviewer.mount('reviewer', { sessionId, target });
   ```

| Autohost admin                                       | Checktiv workspace reviewer                                                                        |
| ---------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| ACL `scope: 'read'`                                  | `session:read`, `evidence:read`, and `report:read`                                                 |
| ACL `scope: 'write'`, and Approve and Decline in `ReservationResults` | `session:decide`. Changing a decision on a completed verification needs `session:override`.        |
| ACL `level: 'listing'`                               | No listing level. A token minted with a `tenant_ref` refuses verifications of other hosts.         |
| `SexOffenders` component                             | The watchlist matches in the reviewer                                                              |
| `ReservationResults` option `styles`                 | `theme` on `workspace(...)`: `primaryColor`, `colorMode`, `logoUrl`, and `brandName`               |
| `ReservationResults` callbacks `success` and `error` | `onEvent` on `mount`, with the events `checktiv.workspace.navigate` and `checktiv.workspace.error` |
| `ReservationResults` callback `revealedIDImage`      | No browser event                                                                                   |

### Approve or decline

Autohost let your server approve or decline a reservation with `POST /reservations/{id}/status`. Checktiv differs in three ways:

- **A person decides.** A reviewer decides a verification that is waiting for review (`status: "awaiting_review"`). The reviewer works in the workspace reviewer or in the console, in **Applicants**. No server API decides a verification that is waiting for review.
- **Rules about the applicant go in the workflow template.** Screening rules read the applicant, the check results, and the fraud signals. An approve rule approves the verification, unless a decline rule also matches or the score reaches the review threshold. A decline rule sends it to review, because Checktiv never declines a verification automatically.
- **Rules about the booking stay on your server.** A screening rule cannot read the dates, the price, or the source of a booking. Apply those rules before the create, and choose a stricter workflow template when they match.

`POST /v1/sessions/{id}/manual_review` is a different operation. It records a decision on a verification that is still in progress (`created`, `pending`, or `in_progress`). On a verification that is waiting for review, it is refused with `409`.

## Step 6: Change endpoints and fields

### Change the base URL and the authentication header

Replace `https://data.autohost.ai/v1` with <code>https://api.us.checktiv.com/v1</code> for the US region, or <code>https://api.eu.checktiv.com/v1</code> for the EU region. Test mode and live mode use the same host: the key decides the mode.

<Code
  code={`# Before (Autohost)\ncurl https://data.autohost.ai/v1/accounts/me \\\n  -H "x-api-key: YOUR_AUTOHOST_API_KEY"\n\n# After (Checktiv)\ncurl https://api.us.checktiv.com/v1/me \\\n  -H "Authorization: Bearer ah_sk_us_test_<your-key>"`}
  lang="bash"
/>

### Map the endpoints

| Autohost Data API                                                              | Checktiv API                                                                                                                                                                                            |
| ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `GET /accounts/me`                                                             | `GET /v1/me`                                                                                                                                                                                            |
| Every `/listings` call                                                         | No equivalent. Remove these calls.                                                                                                                                                                      |
| `POST /reservations`                                                           | `POST /v1/sessions`. It returns the new verification at once, with `status: "pending"`. The result arrives later by webhook.                                                                            |
| `PUT /reservations/{id}`, or a repeat `POST` with the same `confirmation_code` | No equivalent. See [Booking changes](#booking-changes).                                                                                                                                                 |
| A `status: "CANCELED"` update                                                  | `POST /v1/sessions/{id}/cancel`                                                                                                                                                                         |
| `GET /reservations/{id}`, `/status`, `/guestportal`                            | `GET /v1/sessions/{id}`: read `status`, `outcome`, and `review.causes`                                                                                                                                  |
| `GET /reservations/{id}/summary`                                               | `GET /v1/sessions/{id}`: read `check_results`. For a PDF, `GET /v1/sessions/{id}/report.pdf`                                                                                                            |
| `GET /reservations` with `from` and `size`                                     | `GET /v1/sessions` with `limit` and `starting_after`. It filters by `status` and `outcome` only.                                                                                                        |
| `GET /reservations?pending_review=true`                                        | `GET /v1/sessions?status=awaiting_review`                                                                                                                                                               |
| `GET /reservations/{id}?pms=...` (look up by confirmation code)                | No lookup by your own key. Store the verification id with your booking, then read by id. See [Link copied reservations to your bookings](#link-copied-reservations-to-your-bookings).                   |
| `POST /reservations/{id}/status` (`supervised`)                                | The workspace reviewer or the console. See [Approve or decline](#approve-or-decline).                                                                                                                   |
| `GET /reservations/{id}/notes`, and `notes` in a webhook                       | `POST /v1/sessions/{id}/notes` adds a note. `GET /v1/sessions/{id}` returns the notes in `metadata.operatorNotes`.                                                                                      |
| `POST /idcheck/upload/{id}/{step}/{type}`                                      | The SDK, or the verification link. For a document scan with no verification, `POST /v1/id-verification`.                                                                                                |
| `GET /idcheck/status/{id}`                                                     | `GET /v1/sessions/{id}`: the `id_verification` entry in `check_results`                                                                                                                                 |
| `GET /idcheck/image/{id}/{step}`                                               | `GET /v1/sessions/{id}/idv-image/{artifact}`, where `artifact` is `front`, `back`, `selfie`, or `portrait`. It returns the image bytes, not base64. See [Evidence images](/developers/evidence-images). |
| `GET /reservations/{id}/reports/usage-agreement`                               | No API returns the signed document. It is attached to the applicant's record in the console. A copied Autohost reservation does not include it.                                                         |
| `POST /accounts/webhooks`, `DELETE /accounts/webhooks`                         | In the console: **Developers → Webhooks**. No API creates a subscription.                                                                                                                               |
| `?skip_gp=1` (screen without the applicant)                                    | Send the applicant's details on the create, and use a workflow template whose checks all run on the server, such as `watchlist`. The verification then runs without the applicant.                      |
| Other `/reservations/{id}/*` reads, such as `/screens`                         | No equivalent. Read the verification.                                                                                                                                                                   |
| `GET /reservations/{id}/embed`                                                 | The workspace reviewer. See [Step 5](#step-5-replace-the-admin-components).                                                                                                                             |

### Link copied reservations to your bookings

The applicant of each copied verification has the Autohost reservation id in `external_id`. No API call finds a verification by `external_id`, so match them once, after we copy your data:

1. Page through `GET /v1/applicants` with `limit` and `starting_after`. Read `id` and `external_id` from each applicant.
2. Page through `GET /v1/sessions` the same way. Read `id` and `applicant_id` from each verification.
3. Join the two lists on the applicant id. Store each verification id with the booking that has that Autohost reservation id.

This needs the `applicants:read` and `sessions:read` scopes. To find new verifications the same way, send your own booking id as `applicant.external_id` on the create.

### Map the create fields

Autohost took a reservation. Checktiv takes an applicant, a workflow template, and a `metadata` object that describes the transaction.

| Autohost `POST /reservations` field                    | Checktiv `POST /v1/sessions` field                                                                                                   |
| ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ |
| `guest.first_name`                                     | `applicant.given_names`, an array. A middle name is another entry.                                                                   |
| `guest.last_name`                                      | `applicant.family_name`. A surname of several words stays one value.                                                                 |
| `guest.email`                                          | `applicant.email`                                                                                                                    |
| `guest.phone`                                          | `applicant.phone`                                                                                                                    |
| `guest.birth_date`                                     | `applicant.date_of_birth`                                                                                                            |
| `guest.location` (free text)                           | `applicant.address`: an object with `line1`, `city`, and `country` (two uppercase letters). Leave it out if you have only free text. |
| The name on the booking or on the payment card         | `applicant.booking_name`                                                                                                             |
| `confirmation_code`                                    | Your own key in `metadata`                                                                                                           |
| `check_in_date` (with the listing's `check_in_time`)   | `metadata.start_datetime`, ISO 8601 with an explicit offset                                                                          |
| `check_out_date` (with the listing's `check_out_time`) | `metadata.end_datetime`, ISO 8601 with an explicit offset                                                                            |
| `total_price`                                          | `metadata.amount` in minor units, with `metadata.currency`                                                                           |
| `listing_id`, `listing` (the property address)         | `metadata.location`, one line of text                                                                                                |
| `guest_portal_screens`                                 | `workflow_template_id`. One template for each combination of steps you use.                                                          |
| `source`, `status`, `number_of_guests`, `nights`       | Your own keys in `metadata`, if you want them stored with the verification. Nothing in Checktiv reads them.                          |
| `without_listing: true`                                | Remove it. No verification needs a listing.                                                                                          |
| `?sync=1`                                              | Remove it. The create always returns the new verification.                                                                           |
| `?callback=<url>`                                      | A webhook subscription. See [Step 7](#step-7-rewrite-your-webhook-receiver).                                                         |
| `guest_portal_url` in the response                     | `applicant_url`, in the create response only. Or set `send_invite: true` to email the link, if you send `applicant.email`.           |

Details to check in your code:

- **Times need an offset.** Autohost read a time without `Z` in the listing's time zone. Checktiv has no listing, so send the offset of the place where the transaction happens, for example `2026-03-14T15:00:00-05:00`. A value that ends in `Z` is also accepted. A value with no offset is refused.
- **Amounts are in minor units.** Send `125000` with `"currency": "usd"` for 1,250.00. A currency with no decimal places, such as `jpy`, takes the whole number. A decimal string is refused. An `amount` needs a `currency`, and `end_datetime` cannot be before `start_datetime`.
- **A document can make a key required.** If a document in the workflow template prints a `metadata` key, for example the dates or the amount, every create must send that key. A create without it is refused with `403` and the code `esign_requirements_not_met`.
- **Your own metadata keys** take a string, a number, a boolean, or `null`. Some names are reserved, for example `notes`. See [Verification metadata](/developers/session-metadata).
- **Names come in parts.** The create refuses a `full_name` or `legal_name` field. See [Collect user info](/developers/sdks/collect-user-info).
- **Requests are rate limited.** If you create verifications in a batch, handle `429` with the code `rate_limit_exceeded`. See [Rate limits](/developers/rate-limits).

### Booking changes

Autohost treated a repeat `POST /reservations` with the same `confirmation_code` as an update, with no time limit. After you create a Checktiv verification, you cannot point it at another applicant or change its workflow template or your `metadata` keys.

- An `Idempotency-Key` protects retries of one request for 24 hours. A retry with the same body returns the same verification. The same key with a different body returns `409` with the code `idempotency_conflict`.
- If a change matters to the verification, cancel it and create a new one with a new `Idempotency-Key`. A different applicant, or new dates in a document they sign, are changes of this kind. Clear the stored client token when you cancel.
- Keep other booking changes in your own system.

### Map the statuses

Autohost put the verdict in `verification_status.status` and a host decision in `supervised`. Checktiv uses two fields: `status` says where the verification is, and `outcome` says what it decided.

| Autohost                                | Checktiv                                                                                                               |
| --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `status: null` or `status: "pending"`   | Any `status` that is not `completed`, `awaiting_review`, `expired`, or `cancelled`. `outcome` is usually `null`.       |
| `status: "verified"`                    | `status: "completed"`, `outcome: "approved"`, and `decision_source: "automatic"`                                       |
| `status: "review"`                      | `status: "awaiting_review"`. The reasons are in `review.causes`.                                                       |
| A decline by your business rules        | No automatic decline. A decline rule sends the verification to review.                                                 |
| `supervised: "approve"` by a person     | `status: "completed"`, `outcome: "approved"`, and `decision_source: "reviewer"`                                        |
| `supervised: "decline"` by a person     | `status: "completed"`, `outcome: "declined"`, and `decision_source: "reviewer"`                                        |
| No Autohost equivalent: the applicant refuses to sign | `status: "completed"`, `outcome: "declined"`, and no `decision_source`                                                 |
| `reason`, `business_rules`, `top_risks` | `review.causes`, and the `reasons` on each entry of `check_results`. See [Review reasons](/developers/review-reasons). |
| `risk_color`, `protect_score`           | No risk color. Use `outcome` and `review.causes`.                                                                      |
| `pms_status: "CANCELED"`                | `status: "cancelled"`                                                                                                  |

Statuses you did not have in Autohost:

- **`payment_required`.** A live verification waits here when your wallet has no credit, and it sends no webhook. It continues when you add credit. A verification held when it reaches a document-signing step, before the document is shown, does not continue: create a new verification for that applicant.
- **`expired`.** A verification that the applicant does not finish in time ends here. A verification that nobody decides in review also ends here, after a retention period.
- **`awaiting_review` after an error.** A verification can also stop in review after a processing error. Its `review.causes` then includes `processing_error`, and it sends no webhook.

See [Track verification status](/guides/verification/track-status) for every status.

### Map the errors

Autohost returned `{"error": "<message>"}`, mostly with HTTP 400. Checktiv returns an error object with a stable `code`:

```json
{
  "error": {
    "type": "https://docs.checktiv.com/api/errors/validation_error",
    "code": "validation_error",
    "message": "Invalid request body",
    "param": "metadata",
    "request_id": "req_...",
    "doc_url": "https://docs.checktiv.com/api/errors/validation_error",
    "details": {
      "issues": [{ "path": ["metadata", "currency"], "message": "..." }]
    }
  }
}
```

Match on `code`, not on `message`. For a `validation_error`, `details.issues` names each value at fault. Log the `X-Request-Id` response header. Every code has a page in the [error reference](/api/errors).

## Step 7: Rewrite your webhook receiver

Autohost sent each kind of event to its own URL, with no event type in the body, and an optional static `Authorization` header. Checktiv sends every event type you select to one endpoint, with the type in the body and a signature in a header.

Before, with Autohost:

```js
app.post('/webhooks/autohost/screening', express.json(), async (req, res) => {
  if (req.get('authorization') !== process.env.AUTOHOST_WEBHOOK_AUTH) return res.status(401).end();
  await updateBookingStatus(req.body.id, req.body.status); // pending, verified, or review
  res.status(200).end();
});
```

After, with Checktiv:

```js
import crypto from 'node:crypto';

// Read the raw body: the signature covers the exact bytes.
app.post('/webhooks/checktiv', express.raw({ type: 'application/json' }), async (req, res) => {
  const secrets = [
    process.env.CHECKTIV_WEBHOOK_SECRET,
    process.env.CHECKTIV_WEBHOOK_SECRET_NEXT,
  ].filter(Boolean);
  const header = req.get('x-webhook-signature');
  if (!secrets.some((secret) => verifySignature(header, req.body, secret))) {
    res.status(400).end();
    return;
  }
  if (req.get('x-webhook-origin') === 'test_ping') {
    res.status(200).end();
    return;
  }
  const eventId = req.get('x-webhook-id'); // the same on every retry of one event
  if (await alreadyProcessed(eventId)) {
    res.status(200).end();
    return;
  }

  const event = JSON.parse(req.body.toString('utf8'));
  switch (event.type) {
    case 'kyc.session.completed':
    case 'kyc.session.manual_review_requested':
    case 'kyc.session.cancelled':
    case 'kyc.session.expired': {
      const verification = await getVerification(event.sessionId); // GET /v1/sessions/{id}
      await updateBookingFromVerification(verification); // your own code
      break;
    }
    default:
      break; // ignore event types you do not handle
  }
  await markProcessed(eventId);
  res.status(200).end();
});

function verifySignature(header, rawBody, secret) {
  const parts = Object.fromEntries((header ?? '').split(',').map((part) => part.split('=')));
  const timestamp = Number(parts.t);
  if (!parts.v1 || !Number.isFinite(timestamp)) return false;
  if (Math.abs(Date.now() / 1000 - timestamp) > 300) return false; // 5-minute window
  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${parts.t}.`)
    .update(rawBody)
    .digest('hex');
  return (
    expected.length === parts.v1.length &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))
  );
}
```

The signing secret shows once, when you create the subscription or rotate its secret. Rotation takes effect at once: every later delivery, retries and replays included, is signed with the new secret only. The receiver reads a second secret so that you can add the new one in configuration, without a code change. Remove the old one after a delivery verifies with the new one. See [Verify webhook signatures](/developers/webhooks-verify-signatures).

| Autohost webhook setting                                                           | Checktiv event                                                                                                                                                                       |
| ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `screening_webhook_url`                                                            | `kyc.session.completed` for an automatic result, a reviewer decision, or an applicant who refuses to sign. `kyc.session.manual_review_requested` when a verification goes to review. |
| `guest_portal_webhook_url` (the applicant submitted)                               | No server event. Use `onComplete` in the SDK for that moment.                                                                                                                        |
| `reservation_webhook_url` (created or updated)                                     | No equivalent, because you create the verification. Use `kyc.session.cancelled` for a cancellation.                                                                                  |
| Signature submitted (`ElectronicSignature` `onSubmit`, or the `guestportal.signature` reservation field) | `kyc.session.document_signed` and `kyc.session.document_declined`                                                                                                                    |
| `listing_webhook_url`, `account_webhook_url`                                       | No equivalent. Remove these URLs from your Autohost account.                                                                                                                         |
| `stargate_webhook_url`                                                             | No equivalent. See [What is not part of Checktiv](#what-is-not-part-of-checktiv).                                                                                                    |
| A per-request `?callback=<url>`                                                    | The subscription. Route each event in your receiver, for example by the booking you stored with the verification id.                                                                 |

Rules for the receiver:

- **The body has no personal data.** It carries the verification id and, when the event reports one, the outcome. Autohost's `reservation_webhook_url` sent the full reservation, so remove any code that reads applicant or ID details from a webhook body.
- **Webhook keys are camelCase**, for example `sessionId` and `decisionSource`. The REST API uses snake_case, for example `decision_source`. Some keys are optional: see [The event payload](/developers/webhooks#the-event-payload) for when each one is absent, and read `outcome` for the verdict.
- **`kyc.session.expired`** fires when a verification runs out of time before it finishes, or when it stays in review past its retention period.
- **Two cases send no webhook.** A verification that stops on a processing error goes to review without an event, and a verification that waits at `payment_required` sends none. Run `GET /v1/sessions?status=awaiting_review` and `GET /v1/sessions?status=payment_required` on a schedule to find these.

See [Create a webhook endpoint](/developers/webhooks) for every event and its body.

## Step 8: Test

Create verifications with a test-mode key and set `expected_outcome` on the create. Check that your receiver, your booking status, and your admin handle each result:

| `expected_outcome` | Result                                                      | Webhook                               |
| ------------------ | ----------------------------------------------------------- | ------------------------------------- |
| `pass`             | `status: "completed"`, `outcome: "approved"`                | `kyc.session.completed`               |
| `review`           | `status: "awaiting_review"`                                 | `kyc.session.manual_review_requested` |
| `fail`             | `status: "awaiting_review"`. A failed check goes to review. | `kyc.session.manual_review_requested` |
| `doc_quality`      | `status: "awaiting_review"`                                 | `kyc.session.manual_review_requested` |

To test a declined result, decline a verification that waits for review in the workspace reviewer or the console. If a template has a signing step, also refuse to sign once.

Also test:

- **The capture screens.** In test mode, simulated buttons replace the camera. To see the real camera screens, run a few verifications with a live-mode key on a real phone. Live verifications are billed.
- **Every step.** Use a workflow template with every step type you use. Check that a reload after each step shows the next one, and that a missing import shows as `sdk_load_failed`.
- **A webhook with a bad signature, and a repeat `X-Webhook-Id`.** Your receiver must refuse the first and skip the second.
- **An applicant who comes back.** Your server must return the stored client token, not create a second verification.

See [Modes and regions](/developers/modes-and-regions).

## Step 9: Move in stages, then remove Autohost

1. **Record the system for each booking.** Store whether each booking started on Autohost or on Checktiv, and route each booking to its own system. A booking that started on Autohost finishes on Autohost.
2. **Start with a small group.** Start new verifications in Checktiv for one host or one property. Check that webhooks arrive, that your booking status updates, and that your admin shows each result.
3. **Move the rest.** Add more hosts when the first group works. If you find a problem before the end-of-life date, you can start new verifications in Autohost again.
4. **Close what is still open on Autohost.** Before the end-of-life date, create a Checktiv verification for each booking whose Autohost verification is not complete. Send the new verification link to the applicant.
5. **Keep what we do not copy.** Download the Autohost reports and signed documents you must keep.
6. **Remove Autohost.** Remove the Autohost code, script tags, and allowlist entries. In your Autohost account, delete your API keys and SDK keys and remove your webhook URLs.

## Questions

**Do our Autohost API keys work with Checktiv?** No. Create new keys in the console. See [Step 2](#step-2-set-up-your-checktiv-organization).

**Can we run Autohost and Checktiv at the same time?** Yes, until the end-of-life date. Route each booking to the system it started on.

**Do we still send listings?** No. Send the transaction details in `metadata`.

**What happens to Guest Portal links we already sent?** They work until the end-of-life date, then stop. For a booking that still needs a verification, create a Checktiv verification and send its link.

**Can we show the verification link in an iframe?** No. Mount the SDK in your page, or open the link in a new tab.

**Can our server approve or decline a verification?** Not one that is waiting for review. A person decides in the workspace reviewer or the console. Put rules about the applicant in the workflow template, and apply rules about the booking on your server before the create.

**Where does the result come from?** From the webhook, or from `GET /v1/sessions/{id}`. `onComplete` in the SDK is not a result.

**Is testing free?** Yes, in test mode. Live verifications are billed.

## Related pages

- [Quickstart](/developers/quickstart): your first authenticated request
- [Embed the verification flow (SDK)](/developers/sdks/quickstart): the complete SDK walkthrough
- [Token handoff](/developers/sdks/token-handoff): how the client token and browser token work
- [Workspace reviewer](/developers/sdks/workspace-reviewer): the reviewer you embed in your admin dashboard
- [Create a webhook endpoint](/developers/webhooks): events and payloads
- [Sample app](/developers/sdks/sample-app): a complete integration you can run
- [Interactive API reference](/reference): every endpoint and field