---
id: openID4vc-send-verify-credentials-dcapi-ios
title: Verify Credentials with the DC API on iOS (Safari)
hide_title: false
sidebar_label: Send and Verify on iOS (Safari)
slug: /openID4vc-send-verify-credentials-dcapi-ios/
description: Request credentials in Safari on iPhone and iPad over ISO 18013-7 Annex C, same device or by scanning a desktop QR code, and verify the mdoc response.
keywords: [DC API on iOS, Safari Digital Credentials API, ISO 18013-7 Annex C, org-iso-mdoc, mdoc verification iPhone, WebKit credential verification, cross-device QR, Apple Wallet identity, Organisation Wallet, EUDI Wallet, iOS]
---

> **Build this with an AI coding agent.** Install the iGrant.io Agent Skills, then ask your agent to build the integration:
>
> ```bash
> npx skills add L3-iGrant/skills
> ```
>
> This page maps to the `igrantio-dcapi-ios` skill. Install just that one with:
>
> ```bash
> npx skills add L3-iGrant/skills -s igrantio-dcapi-ios
> ```


import Link from "@docusaurus/Link";
import NoteBox from "@site/src/components/NoteBox";
import YouTubeEmbed from "@site/src/components/YouTubeEmbed";

On iPhone and iPad the W3C Digital Credentials API (DC API) is a **Safari** feature. Your page calls `navigator.credentials.get()` from a web context, WebKit validates the call against the page origin, and iOS then routes it to the wallet layer, which asks the holder to authorise the release of the requested claims. Because every browser on iOS is built on WebKit, the behaviour you get in Safari is the behaviour you get everywhere on the platform.

Two things make this materially different from <Link to="/docs/openID4vc-send-verify-credentials-dcapi-android/">DC API verification on Android with Chrome</Link>, and they shape the whole of this guide:

* **One protocol, not two.** Safari implements ISO/IEC 18013-7 Annex C (protocol identifier `org-iso-mdoc`) and does not offer OpenID4VP over the DC API. Requests are therefore always signed mdoc requests, and the credential format is always an ISO/IEC 18013-5 mdoc.
* **No open provider chooser.** Android hands the request to Credential Manager, where any installed wallet may register as a provider. iOS has no equivalent open registry surfaced to the web, so which wallet can answer, and which document types it may answer with, is constrained by the platform rather than by your request.

This guide otherwise follows the same send and verify flow as the <Link to="/docs/openID4vc-send-verify-credentials/">Send and Verify Credential</Link> guide, and uses the same iGrant.io APIs.

As a verifier on iOS you will:

- Define the claims you require in a presentation definition, fixed to Annex C and to a signed DC API response mode.
- Declare the origins your request may legitimately come from, since a signed request without `expectedOrigins` cannot be checked by the wallet.
- Invoke the request from Safari, on the iPhone itself or by having the iPhone scan a QR code shown by a desktop browser.
- Decrypt and validate the mdoc response returned to your page.

<NoteBox title="DC API prerequisite on iOS:" variant="tip">
<ol>
<li>Safari 26 or later, which is when the Digital Credentials API first shipped in a stable Apple browser (September 2025). There is no flag to enable it on an older version, so older devices need a fallback path rather than a setting change.</li>
<li>A page served over HTTPS from the origin you list in <code>expectedOrigins</code>. The wallet compares the calling origin against that list and will reject a mismatch.</li>
<li>For the cross-device flow (desktop browser, iPhone wallet), Bluetooth enabled on both devices, kept near each other for the proximity check.</li>
<li>An age verification credential in your Data Wallet before trialling; see the <Link to="/docs/eu-age-verification/">Age Verification workflow</Link>.</li>
</ol>
</NoteBox>

### Step 1: Get the API Key

To obtain your API key, please contact [support@igrant.io](mailto:support@igrant.io?subject=Request%20API%20Key). Once you have received your API key, enter it in the field below and click the **Set API Key** button to save it for future use.

import { ApiKeyManager } from "@site/src/components/ApiKeyManager";

<ApiKeyManager />

### Step 2: Create Presentation Definition (Verifier Admin)

To create a presentation definition for requesting proof, you can run the code block below using the **Run** button. Alternatively, you can manually copy the code block and use it in the body of the API request provided [here](/docs/openid4vc-api/config-digital-wallet-open-id-presentation-definition). On iOS, DC API runs over ISO 18013-7 Annex C only, so the **Request** panel below comes preconfigured: the credential format is ISO/IEC 18013-5 mdoc/mDL and the response mode is **DC API, Signed**. Because the request is signed, `expectedOrigins` is required. It is prefilled with the origin this page is served from, so you can run the request straight away. In your own application, set it to your verifier origin so the wallet can detect replay from a malicious verifier.

The protocol and response mode selectors are locked here on purpose. Choosing OpenID4VP or an unsigned response mode would produce a request that Safari cannot dispatch, so the panel removes those options rather than letting you build one that fails at `navigator.credentials.get()` time.

import Step2 from "@site/src/components/VerificationDcApi/Step2";

<Step2 platform="ios" />

<NoteBox title="Tip:" variant="tip">
Once a presentation definition is created, the <code>presentationDefinitionId</code> can be reused to verify multiple credentials (Step 3).
</NoteBox>

### Step 3: Create Verification Request (Verifier/Relying Party)

Now you can ask a wallet for the credential you defined in Step 2. When Step 2 succeeds it fills the `presentationDefinitionId` into the request below, and that id tells the wallet which credential to present, so check that it matches the one Step 2 returned before you click **Run**. Leave `requestByReference` set to `true`, which sends the wallet a link to the request instead of the whole request and keeps larger requests working smoothly. You can also copy the code block and use it in the body of the API request provided [here](/docs/openid4vc-api/config-create-digital-wallet-open-id-verification-request-v-3).

Once the request succeeds, the **Request Credential** button becomes active. Clicking it hands the request to the browser, which invokes the wallet. Step 4 covers what happens next.

import Step3 from "@site/src/components/VerificationDcApi/Step3";

<Step3
  initialJsonData={{
    presentationDefinitionId: "<presentationDefinitionId>",
    requestByReference: true,
  }}
/>

The presentation definition provided adheres to the specifications outlined in the DIF Presentation Exchange standard, as detailed [here](https://identity.foundation/presentation-exchange/).

The response the API returns carries a per-browser request object. The client library picks the Safari variant when it detects a WebKit user agent and passes its `protocol` and `data` straight into `navigator.credentials.get()`, so you do not build the Annex C payload by hand.

### Step 4: Send and Receive Verifiable Presentation (iOS Device)

Clicking **Request Credential** calls the DC API from the page. iOS then routes the request out of the browser and into the wallet layer, and what the holder sees depends on where that wallet is.

**Same device (browsing in Safari on the iPhone or iPad).** iOS presents the request in a system-controlled sheet on top of the page. The holder reviews the claims being asked for, authorises the release, and control returns to Safari with the response. There is no QR code and no camera step in this case, and the sheet is drawn by the operating system rather than by your page, so you cannot style or script it.

**Cross device (browsing on a desktop, wallet on the iPhone).** The desktop browser displays a QR code that encodes the hand-off, not the credential. Scanning it with the iPhone hands the request to the wallet on the phone. The two devices then establish an end-to-end encrypted channel and complete a Bluetooth proximity check, which is why both need Bluetooth on and need to be physically close. The holder consents on the phone, and the response travels back to the desktop page that made the call, so the desktop tab must stay open for the whole exchange.

In both cases the holder sees the requested claims before anything leaves the device, and because the credential is an mdoc, selective disclosure applies: an age check can return only an "over 18" result rather than a date of birth.

Once the holder consents, the credential is returned to the page, a green **Credential shared successfully** message appears above the panels, the **Request Credential** button changes to **Credential Received**, and the **Response** panel shows what came back. An Annex C response arrives as a base64 `Response` field holding an encrypted mdoc response, which is forwarded to the receive endpoint for decryption, so the raw value in the panel is not readable until the backend has processed it. If the holder dismisses the sheet, the API rejects with `NotAllowedError` or `AbortError`, which the page reports as a cancelled wallet operation rather than a failure of your request.

### Step 5: Send and Receive Verifiable Presentation (Verifier/Relying Party)

* The verifier (relying party) receives the requested credentials and can verify them. Read the received credential by executing the [Read Verification History API](/docs/openid4vc-api/config-read-digital-wallet-open-id-verification-history-v-3).
* From the response received, the `vpTokenResponse` can be decoded using the [JWT Decoder](/docs/devtools-jwt-decoder/).
* Because the credential itself is an ISO/IEC 18013-5 mdoc rather than a JWT, use the [mdoc Decoder](/docs/devtools-mdoc-decoder/) to inspect the namespaces and the individual disclosed elements.

:::info Trust status of the verifier
Annex C authenticates the verifier to the wallet through reader authentication, so the holder is shown who is asking before any element is released. On top of that, the wallet checks whether the verifier can be matched against a recognised trust list, exposed in the response through the `isVerifiedWithTrustList` and `trustServiceProvider` fields. If the verifier cannot be matched, the Data Wallet shows a **"Service provider not verified"** warning. See [The "Service provider not verified" notice](/docs/trust-untrusted-service-provider/) and [Trust in the Wallet Ecosystem](/docs/trust-overview/).
:::

### Try it Yourself

Watch a short demo of verifying credentials with the Digital Credentials API in Safari on iOS:

<YouTubeEmbed
  videoId="j8cts3VnKYY"
  title="Verify Credentials in Safari on iOS with the DC API over ISO 18013-7 Annex C"
  description="An iOS walkthrough: a verifier (relying party) requests verifiable credentials from an EUDI Wallet in Safari on iPhone over the W3C Digital Credentials API, using ISO 18013-7 Annex C (protocol org-iso-mdoc) with a signed request and expectedOrigins. The demo shows the system consent sheet on the device, the holder authorising the release of selected mdoc elements, and the verifier decrypting and validating the response."
  thumbnailUrl="https://i.ytimg.com/vi/j8cts3VnKYY/maxresdefault.jpg"
  uploadDate="2026-07-18"
  duration="PT2M1S"
  watchLinkText="Watch the iOS DC API demo on YouTube"
/>

### Frequently asked questions about the DC API on iOS

**Which protocols can I request in Safari?**
Only ISO/IEC 18013-7 Annex C, identified as `org-iso-mdoc`. OpenID4VP over the DC API is not implemented in Safari, so a request built with `responseMode: dc_api` for OpenID4VP will not reach a wallet on iOS. If your verifier supports both platforms, build the request per platform rather than sending one shape everywhere.

**Will this work in Chrome or Firefox on my iPhone?**
Those browsers use WebKit on iOS, so they inherit Safari's DC API implementation and its Annex C limitation. You do not get Chrome's OpenID4VP support by installing Chrome on an iPhone. Test against Safari and treat the others as the same engine.

**Which credentials can actually be requested on iOS?**
Requests are limited to ISO 18013-5 mdocs, and iOS recognises only a fixed set of five mobile document type categories defined by Apple's Identity Document Services: EU age verification, EU personal ID (PID), photo ID, the Japan MyNumber card, and the mobile driving licence (mDL). A document is only requestable if the holder has provisioned one of these on the device. The two EU categories, age verification and PID, are the ones most relevant to European Digital Identity use cases. See <Link to="/docs/openID4vc-dcapi-overview/">the DC API overview</Link> for the identifiers and constraints.

**Do I need a certificate or registration from Apple to act as a verifier?**
Annex C requires reader authentication, so your request is signed with a certificate the wallet can check, and signed requests must carry `expectedOrigins` matching the page that calls the API. Where the credential comes from Apple Wallet, Apple additionally requires the verifier to obtain a signing certificate from Apple and use the Verify with Wallet mechanism. Plan for certificate issuance and renewal as part of your release process, not as a one-off setup step.

**Can a native iOS app request credentials without a browser?**
No. The DC API is a web platform API, and Apple has not shipped a native app-to-app verification equivalent, so a native iOS verifier needs a web view to make the call. A native app can act as a credential provider, but not as a requester. On Android the same capability is also exposed natively through Credential Manager, which is a genuine architectural difference between the two platforms rather than a temporary gap.

**Why does my request succeed on Android but fail on iOS?**
The usual causes, in order: the request was built for OpenID4VP instead of Annex C, `expectedOrigins` does not match the origin actually serving the page (a staging host or a preview URL is a common culprit), the credential requested is not one iOS can present, or the Safari version predates 26. A cancelled sheet reports as `NotAllowedError` or `AbortError`, which means the holder declined rather than that the request was malformed.

### Where to go next

* <Link to="/docs/openID4vc-dcapi-overview/">Digital Credentials API overview</Link>, for the specification's maturity, browser support and the limitations to plan around.
* <Link to="/docs/openID4vc-send-verify-credentials-dcapi-android/">DC API verification on Android with Chrome</Link>, where Credential Manager and OpenID4VP change the flow.
* <Link to="/docs/openID4vc-send-verify-credentials/">OpenID4VP send and verify credentials</Link>, the redirect-based flow to fall back on for browsers and devices where the DC API is unavailable.
