> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sajn.se/llms.txt
> Use this file to discover all available pages before exploring further.

# Verify identity with sajn ID

> Send a BankID identity check to a person by email or SMS, and get the result by webhook or by polling

In this guide, you verify a person's identity with sajn ID. sajn sends the person a link by email or SMS, the person identifies with Swedish BankID, and you get the result. Use it before you grant access to a service, open an account, or send a contract.

sajn ID checks the identity in one of two ways:

* With a national identity number: when you send `nationalId`, BankID checks that the person has that number.
* With the name: without `nationalId`, BankID checks that the person's name matches `fullName`. Use this when you don't need the identity number.

## Before you begin

* Store your API key in the `SAJN_API_KEY` environment variable. To create a key, go to workspace settings in the sajn app, then **Utvecklare** (Developer) > **API-nycklar** (API keys).
* Subscribe a webhook endpoint to `identity_check.verified`, `identity_check.failed`, and `identity_check.cancelled`. For more information, see [Webhooks](/webhooks/overview).
* Each check counts toward your organization's monthly identity check quota.

## Run an identity check

<Steps>
  <Step title="Create the check">
    Send a `POST` request to `/api/v1/identity-checks`. Creating the check also sends it, so there's no separate send step:

    <CodeGroup>
      ```bash curl theme={null}
      curl -X POST https://app.sajn.se/api/v1/identity-checks \
        -H "Authorization: Bearer $SAJN_API_KEY" \
        -H "Sajn-Version: 2026-10" \
        -H "Content-Type: application/json" \
        -d '{
          "fullName": "Alex Andersson",
          "channel": "EMAIL",
          "email": "alex@example.com",
          "reference": "signup-4711",
          "language": "en"
        }'
      ```

      ```javascript Node.js theme={null}
      const response = await fetch("https://app.sajn.se/api/v1/identity-checks", {
        method: "POST",
        headers: {
          Authorization: `Bearer ${process.env.SAJN_API_KEY}`,
          "Sajn-Version": "2026-10",
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          fullName: "Alex Andersson",
          channel: "EMAIL",
          email: "alex@example.com",
          reference: "signup-4711",
          language: "en",
        }),
      });
      const check = await response.json();
      console.log(check.id, check.status);
      ```

      ```python Python theme={null}
      import os

      import requests

      response = requests.post(
          "https://app.sajn.se/api/v1/identity-checks",
          headers={
              "Authorization": f"Bearer {os.environ['SAJN_API_KEY']}",
              "Sajn-Version": "2026-10",
          },
          json={
              "fullName": "Alex Andersson",
              "channel": "EMAIL",
              "email": "alex@example.com",
              "reference": "signup-4711",
              "language": "en",
          },
      )
      response.raise_for_status()
      check = response.json()
      print(check["id"], check["status"])
      ```
    </CodeGroup>

    The request takes the following fields:

    * `fullName` and `channel` are required. With `"channel": "SMS"`, send `phone` instead of `email`.
    * `nationalId` makes BankID check the identity number.
    * `reference` is your own ID for the check, returned in the check and its webhook events.
    * `contactId` links the check to a contact.
    * `language` is the language of the messages, as an ISO 639-1 code such as `en`. The default is `sv`.
    * `expiresAt` is when the link stops working. The default is seven days after creation.

    The response is the check. Store its `id`. Only this response has `verificationUrl`, in case you want to show the link yourself:

    ```json theme={null}
    {
      "id": "cm4k2xm3a0012abcd7913wxyz",
      "organizationId": "cm4k2xz0a0000abcd0000orgx",
      "fullName": "Alex Andersson",
      "email": "alex@example.com",
      "phone": null,
      "channel": "EMAIL",
      "status": "SENT",
      "reference": "signup-4711",
      "language": "en",
      "expiresAt": "2026-10-08T09:00:00.000Z",
      "verifiedAt": null,
      "createdAt": "2026-10-01T09:00:00.000Z",
      "updatedAt": "2026-10-01T09:00:00.000Z",
      "verificationUrl": "https://id.sajn.se/v/R4nd0mT0k3n",
      "contact": null,
      "createdBy": { "id": "cm4k2xw3c0000abcd0000usrx", "email": "dana@example.com", "name": "Dana Lund" },
      "audits": null,
      "data": null
    }
    ```
  </Step>

  <Step title="Get the result">
    When the person finishes, sajn sends an `identity_check.verified` event. A failed or cancelled check sends `identity_check.failed` or `identity_check.cancelled`, and `identity_check.failed` adds the reason in `data.failureReason`. The event's `data.object` is the check, so find your record by its `reference`:

    ```javascript theme={null}
    // Inside your webhook handler, after you verify the signature.
    const check = event.data.object;
    if (event.type === "identity_check.verified") {
      await markVerified(check.reference, check.verifiedAt);
    }
    if (event.type === "identity_check.failed") {
      await markFailed(check.reference, event.data.failureReason);
    }
    ```

    `markVerified` and `markFailed` stand for your own code. To verify the signature, see [Verify webhook signatures](/webhooks/verify-signatures). For the full event, see [Webhook payloads](/webhooks/payloads).

    To read the check without webhooks, poll it:

    ```bash theme={null}
    curl https://app.sajn.se/api/v1/identity-checks/CHECK_ID \
      -H "Authorization: Bearer $SAJN_API_KEY" \
      -H "Sajn-Version: 2026-10"
    ```

    Replace `CHECK_ID` with the check `id`. `status` moves through `CREATED`, `SENT`, `OPENED`, and ends at `VERIFIED`, `FAILED`, `EXPIRED`, or `CANCELLED`. A verified check also has `verifiedAt`, the BankID result in `data`, and its event history in `audits`.
  </Step>
</Steps>

The BankID result in `data` contains personal data, such as the identity number. Store only what you need.

## List checks

`GET /api/v1/identity-checks` lists the organization's checks, newest first. Filter with `status`, such as `status=VERIFIED,FAILED`, and with `createdAfter`, `createdBefore`, `updatedAfter`, and `updatedBefore`. The response has the checks in `data`, each with `audits`, `data`, and `verificationUrl` set to `null`. To read `audits` and `data`, get the check by ID. While `hasMore` is `true`, pass `nextCursor` as `cursor` to get the next page. For more information, see [Pagination](/api-fundamentals/pagination).

## Handle errors

* `400 VALIDATION_FAILED`: `fullName` or `channel` is missing, the channel's address is missing, or `expiresAt` isn't in the future.
* A check with `status` `FAILED`: the person canceled, BankID failed, or the name or identity number didn't match. The person can open the link again until it expires. Otherwise, create a new check.
* A check with `status` `EXPIRED`: the person didn't finish before `expiresAt`. Create a new check.

For the error shape and every code, see [Errors](/api-fundamentals/errors).

## Next steps

<CardGroup cols={2}>
  <Card title="Verify identity before signing" icon="id-card" href="/guides/recipes/verify-identity-before-signing">
    Run a sajn ID check, then send a contract.
  </Card>

  <Card title="sajn ID" icon="shield-check" href="/concepts/sajn-id">
    Learn how sajn ID works.
  </Card>

  <Card title="Set the signing method" icon="fingerprint" href="/guides/identity/signing-methods">
    Verify identity as part of signing instead.
  </Card>

  <Card title="Create an identity check" icon="code" href="/api-reference/create-an-identity-check">
    See the endpoint in the API reference.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.