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

# Build Backend Integration

> Implement the /verifyIdDoc endpoint to evaluate identity document verification results.

Add this endpoint alongside your existing [server-side endpoints](/building-blocks/build-server-endpoints) at the callback URL configured for your OwnID application, for example:

```http theme={null}
POST https://example.com/api/ownid/verifyIdDoc
```

OwnID sends this request when the user completes the hosted document verification step. Your endpoint should validate the request signature, inspect the verification result, compare it with your account data, and return a decision.

<Warning>
  Always validate the `ownid-signature` and `ownid-timestamp` headers before trusting the request body. See [Validating Signatures](/building-blocks/security#validating-signatures) for the validation process.
</Warning>

## Request Body

The request body includes the user identifier and the extracted document data.

<Tabs>
  <Tab title="Passport">
    ```json theme={null}
    {
      "loginId": "user@example.com",
      "type": "PASSPORT",
      "data": {
        "number": "X12345678",
        "firstName": "Alex",
        "lastName": "Morgan",
        "gender": "F",
        "dateOfBirth": "1990-05-14",
        "dateOfIssue": "2020-01-10",
        "dateOfExpiry": "2030-01-09",
        "placeOfBirth": "BRAZIL",
        "nationality": "BRAZIL"
      }
    }
    ```
  </Tab>

  <Tab title="Driver's License">
    ```json theme={null}
    {
      "loginId": "user@example.com",
      "type": "DRIVERLICENSE",
      "data": {
        "number": "D1234567",
        "firstName": "Alex",
        "lastName": "Morgan",
        "gender": "F",
        "dateOfBirth": "1990-05-14",
        "dateOfIssue": "2021-03-01",
        "dateOfExpiry": "2029-03-01"
      }
    }
    ```
  </Tab>
</Tabs>

| Field               | Type   | Description                                                    |
| ------------------- | ------ | -------------------------------------------------------------- |
| `loginId`           | string | User identifier supplied when the challenge was started.       |
| `type`              | string | Document type. One of `PASSPORT` or `DRIVERLICENSE`.           |
| `data.number`       | string | Full document number.                                          |
| `data.firstName`    | string | First name as it appears on the document.                      |
| `data.lastName`     | string | Last name as it appears on the document.                       |
| `data.gender`       | string | Gender as it appears on the document. One of `F`, `M`, or `O`. |
| `data.dateOfBirth`  | string | Date of birth in ISO 8601 format (`YYYY-MM-DD`).               |
| `data.dateOfIssue`  | string | Document issue date in ISO 8601 format.                        |
| `data.dateOfExpiry` | string | Document expiry date in ISO 8601 format.                       |
| `data.placeOfBirth` | string | Place of birth. Passport only.                                 |
| `data.nationality`  | string | Nationality as it appears on the document. Passport only.      |

## Responses

Return one of the following HTTP code responses:

<CodeGroup>
  ```text 204 Approve theme={null}
    {{NO CONTENT}}
  ```

  ```json 403 Reject theme={null}
  {
    "reason": "name mismatch" // never pass PII, only for debugging purposes
  }
  ```

  ```text 404 Account Not Found theme={null}
    {{NO CONTENT}}
  ```
</CodeGroup>

## Example Implementation

<CodeGroup>
  ```javascript Node.js theme={null}
  router.post('/verifyIdDoc', async (req, res) => {
    validateOwnIdSignature(req); // See Validating Signatures.

    const { loginId, type, data } = req.body;

    const user = await users.findByLoginId(loginId);
    if (!user) {
      return res.status(404).send();
    }

    const nameMatches =
      data.firstName.toLowerCase() === user.firstName.toLowerCase() &&
      data.lastName.toLowerCase() === user.lastName.toLowerCase();

    if (!nameMatches) {
      return res.status(403).json({ reason: 'name mismatch' });
    }

    return res.status(204).send();
  });
  ```

  ```java Java theme={null}
  @PostMapping("/verifyIdDoc")
  public ResponseEntity<Object> verifyIdDoc(@RequestBody VerifyIdDocRequest req) {
      validateOwnIdSignature(req); // See Validating Signatures.

      User user = userRepository.findByLoginId(req.getLoginId());
      if (user == null) {
          return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
      }

      boolean nameMatches =
          req.getData().getFirstName().equalsIgnoreCase(user.getFirstName()) &&
          req.getData().getLastName().equalsIgnoreCase(user.getLastName());

      if (!nameMatches) {
          return ResponseEntity.status(HttpStatus.FORBIDDEN)
              .body(Map.of("reason", "name mismatch"));
      }

      return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
  }
  ```

  ```csharp C# theme={null}
  [HttpPost("verifyIdDoc")]
  public async Task<ActionResult> VerifyIdDoc([FromBody] VerifyIdDocRequest req)
  {
      ValidateOwnIdSignature(req); // See Validating Signatures.

      var user = await _userRepository.FindByLoginIdAsync(req.LoginId);
      if (user == null)
      {
          return NotFound();
      }

      var nameMatches =
          string.Equals(req.Data.FirstName, user.FirstName, StringComparison.OrdinalIgnoreCase) &&
          string.Equals(req.Data.LastName, user.LastName, StringComparison.OrdinalIgnoreCase);

      if (!nameMatches)
      {
          return StatusCode(403, new { reason = "name mismatch" });
      }

      return NoContent();
  }
  ```

  ```python Python theme={null}
  @app.route('/verifyIdDoc', methods=['POST'])
  def verify_id_doc():
      validate_ownid_signature(request)  # See Validating Signatures.

      body = request.get_json()
      login_id = body['loginId']
      data = body['data']

      user = db.session.query(User).filter(User.email == login_id).first()
      if not user:
          return ('', 404)

      name_matches = (
          data['firstName'].lower() == user.first_name.lower() and
          data['lastName'].lower() == user.last_name.lower()
      )

      if not name_matches:
          return ({'reason': 'name mismatch'}, 403)

      return ('', 204)
  ```
</CodeGroup>
