Skip to main content

Remote issuance

Remote issuance allows you to issue credentials to recipients who are not present at the time of issuance.

tip

Remote issuance is easier to implement than in-person issuance, since no user-interface is required. If you want control over the issuance process and user interface, use in-person issuance.

note

Remote issuance requests and their completed credentials are visible together with in-person issuances in the Credentials view in Composer.

Verification options

Described below are the two methods by which the recipient can accept remote issuances:

  1. Verifying using a one-time passcode (OTP) sent via SMS or email
  2. Signing in to the Concierge using an existing active credential
note

Signing in to the Concierge is the recommended method for recipients who already have an active credential. This method is easier for the issuer (verification SMS or email is not required) and provides a good user experience.

OTP verification

The OTP issuance flow is as follows:

  • The recipient receives an email or SMS containing a link to accept the credential via the VO Concierge website.
  • The recipient opens the link and is verified by an SMS or email as second-factor authentication via an OTP (one-time passcode).
    • If the recipient is not verified, the issuance is not completed
  • Optionally, a guided photo-capture flow is used to capture a photo of the recipient for credential presentation in combination with Face Check.
  • The recipient adds the credential to their wallet.
    • On a desktop, the recipient is prompted to scan the QR code to complete the issuance on their mobile device.
    • On a mobile device, the wallet is opened via a deep link, and recipient is prompted to save their verifiable credential.
      • If the recipient does not have a wallet, they will be prompted to install the Microsoft Authenticator app.
remote issuance interaction diagramremote issuance interaction diagram

Concierge sign-in verification

The Concierge sign-in issuance flow is as follows:

  • The recipient receives an email or SMS containing a link to accept the credential via the VO Concierge website.
  • The receipient opens the link, if not already signed-in to the Concierge, they are prompted to sign-in.
  • Optionally, a guided photo-capture flow is used to capture a photo of the recipient for credential presentation in combination with Face Check.
  • The recipient adds the credential to their wallet.
    • On a desktop, the recipient is prompted to scan the QR code to complete the issuance on their mobile device.
    • On a mobile device, the wallet is opened via a deep link, and recipient is prompted to save their verifiable credential.
      • If the recipient does not have a wallet, they will be prompted to install the Microsoft Authenticator app.

Alternatively, the recipient can sign in to the Concierge at / and see all pending issuances at the top of their home page. They can select each pending issuance to accept the credential.

remote issuance via concierge interaction diagramremote issuance via concierge interaction diagram

Notification options

The VO platform can send a notification to the recipient via email or SMS. The recipient will receive a link to complete the issuance.

If you prefer to send your own notification, you can include a link directly for the applicable issuance flow in your own email or SMS (see issuance URLs).

Issuance URLs

Issuance URLs for the two verification methods are as follows, where the $issuanceId is the ID of the remote issuance:

  • OTP verification at /issuance/$issuanceId
  • Concierge sign-in verification at /issue/$issuanceId

Alternatively, issuees can sign in to the Concierge and see all pending issuances at the top of their home page. They can click or tap on each pending issuance to accept the credential.

Bulk remote issuance

Remote issuance supports bulk credential issuing to many recipients, either via app integration with the VO API or manually via the Composer.

warning

Both the Composer and the API have a hard limit of creating no more than 1000 remote issuances at one time.

  • When using the the Composer, you can split the CSV file into multiple files and upload them separately.
  • When using the API, you can make multiple requests with a maximum of 1000 remote issuances per request.

Manual remote issuance

The Composer provides the ability to:

  • Create remote issuances by clicking 'Issue Credential' when viewing a verifiable credential.
  • Create bulk remote issuances from a CSV file by clicking 'Bulk Issue Credential' when viewing a verifiable credential.
  • The 'Remote Issuances' section of the Composer allows you to list, filter, cancel, or view the details of remote issuances.

CSV columns

When bulk issuing through The Composer, you will upload CSV files with data arranged into specific columns. There are fixed columns (which are always required) and claim-data columns (which depend on the fields defined in the contract).

Each row represents a single async issuance request and each column corresponds to a field.

ColumnInput mappingNote
Identity IdentifierAsyncIssuanceRequestInput.identity.identifierUnique identifier of the identity (see Identity mapping guide)
Identity IssuerAsyncIssuanceRequestInput.identity.issuerIssuer of the identity (see Identity mapping guide)
Recipient NameAsyncIssuanceRequestInput.identity.nameName of the identity (see Identity mapping guide)
(Optional) Notification Method (email | sms)

AsyncIssuanceRequestInput.contact.notification.method

"email" or "sms"
(Optional) Notification Value

AsyncIssuanceRequestInput.contact.notification.value

Email or phone number for notification of issuance
(Optional) Verification Method (email | sms)

AsyncIssuanceRequestInput.contact.verification.method

"email" or "sms"
(Optional) Verification Value

AsyncIssuanceRequestInput.contact.verification.value

Email or phone number for verification of issuance (OTP)
Issuance Expiry (oneDay | oneMonth | oneWeek | threeDays | threeMonths | twoWeeks)

AsyncIssuanceRequestInput.expiry

"oneDay" or "threeDays" or "oneWeek" or "twoWeeks" or "oneMonth" or "threeMonths"

(Optional) - Credential Expiry Date

AsyncIssuanceRequestInput.expirationDate

Date-time string in ISO 8601 format
(Optional) - Post issuance redirect url

AsyncIssuanceRequestInput.postIssuanceRedirectUrl

Redirect to this URL after issuance
note

"(Optional)" prefix means the column is not required.

Create remote issuance request

tip

To create a remote issuance request definition and explore the API, use the issuance builder and select 'Remote' as the 'Delivery method' option, as a starting point.

The async issuance request input specifies:

  1. Which credential will be issued.
  2. The identity to whom the credential will be issued (recipient / issuee).
  3. The contact details of the recipient for notifications and verification (both optional) during the issuance flow.
  4. The values of any claims defined by the contract.
  5. The expiration of the remote issuance (not the credential), after which the issuance cannot be completed.
  6. Other optional input:
    1. A callback URI if issuance event data should also be sent to a server-side endpoint.
    2. An expiration date for explicit control over when a credential expires, regardless of when it is issued.
    3. A request to capture a photo of the recipient during issuance if the credential requires a face check photo and the photo is not included with the claim data provided.
    4. A post issuance redirect url where the user will be redirected to after a successful issuance
note

PII (personally identifiable information) is deleted after the remote issuance is completed, is cancelled or at expiry.

createAsyncIssuanceRequest mutation: will return a response with async issuance IDs or validation errors
mutation CreateAsyncIssuanceRequest($request: [AsyncIssuanceRequestInput!]!) {
createAsyncIssuanceRequest(request: $request) {
... on AsyncIssuanceResponse {
asyncIssuanceRequestIds
}
... on AsyncIssuanceErrorResponse {
errors
}
}
}
Example variables with optional callback and PIN defined
{
"request": {
"contractId": "<id of the contract to be issued>",
"identityId": "<id of identity of recipient; or specify via identity field>",
"expirationDate": "<explicit expiry date of the credential in ISO format>",
"expiry": "<expiry period of the remote issuance, if not completed>",
"photoCapture": true, // include photo capture in the issuance process to support Face Check
"claims": {
"claimName": "claimValue" // claim data specified by the contract that must be included in the credential
},
"contact": {
"notification": {
"method": "email",
"value": "name@outlook.com"
},
"verification": {
"method": "sms",
"value": "+61412123456"
}
}
}
}

If the async issuance request is successful, the response will contain the async issuance request IDs.

Example response of the async issuance request
{
"data": {
"createAsyncIssuanceRequest": {
"asyncIssuanceRequestIds": ["2bdb43e8-c85a-48c1-8541-ef634e5dba05", "6372d603-bfe7-4ba8-91d6-771a9b61fb32"]
}
}
}

Handling errors

createAsyncIssuanceRequest returns errors through two mechanically different channels. Read both: a client that only inspects ... on AsyncIssuanceErrorResponse will see an unexplained null result for anything in the second channel.

Per-request validation errors

These are input validation failures against a specific item in the request array. They are returned as AsyncIssuanceErrorResponse.errors, and the mutation still resolves successfully.

warning

Validation is all-or-nothing. If any item in the batch fails validation, no async issuance requests are created — not even for the items that passed. Correct the flagged items and resubmit the whole batch.

The errors array is positional and sparse:

  • errors[i] corresponds to request[i], with null where that item had no validation error.
  • The array can be shorter than the input array. Only entries up to the last failing index are present — trailing items that had no error are omitted, not padded with null.
  • A null entry means "no validation error was found for this item" — because the batch is atomic, it does not mean that item was created.
Example error response: request 2 fails validation, so nothing is created
{
"data": {
"createAsyncIssuanceRequest": {
"errors": [null, "Claims must include: card_number"]
}
}
}

See reference: per-request validation errors for the full list.

Whole-call errors

Anything thrown outside the per-request validation loop — authorisation, feature-flag checks, the 1000-request cap, Concierge sign-in eligibility, or identity resolution — aborts the entire mutation. data.createAsyncIssuanceRequest is null and neither union member resolves, so these only appear in the top-level GraphQL errors array.

Example error response: caller lacks issuer permission
{
"data": {
"createAsyncIssuanceRequest": null
},
"errors": [
{
"message": "Not Authorized!",
"extensions": {
"code": "FORBIDDEN",
"http": { "status": 403 }
}
}
]
}

See reference: whole-call errors for the full list.

note

Don't treat error message text as a stable, long-term contract — there is no error-code enum on this mutation, and wording may change. Branch on response shape first (AsyncIssuanceErrorResponse vs. top-level errors). Within the top-level errors array, two categories currently carry a stable extensions.code: the authorisation failure uses FORBIDDEN, and GraphQL schema/scalar errors (malformed UUID, DateTime, URL or JSONObject values, and constraint violations such as givenName/surname outside 1–255 characters) use BAD_USER_INPUT. The remaining whole-call errors (the 1000-request cap, feature-flag checks, the batch-IDV restriction, Concierge sign-in eligibility, identity resolution) do not currently expose an equivalent stable code, so their message text is presently the only way to tell them apart.

Errors described here are validation-time only. Failures that occur after the requests are created (recipient rejects, request expires, IDV name mismatch) surface on the async issuance request itself via status, failureReason, and the machine-readable failureReasonCode (rejected, expired, failed, name-mismatch).

Reference: per-request validation errors

Contract and claims

ErrorCause
Contract could not be foundcontractId doesn't match a contract
Contract must be provisioned before issuanceContract exists but isn't published
Contract must not be deprecatedContract has been deprecated
Claims must include: <claims>Required, non-optional contract claims missing from claims
Claims must not include any of: issuanceId, name, identityId, photoReserved claims supplied
[Claim Type: <type>] <detail>A claim value failed the contract's type/validation rules, e.g. length, range, regex, list, date, email, URL, phone, among others
Unsupported claim type: <type>Contract declares a claim type that isn't supported

Recipient and contact details

ErrorCause
Identity could not be foundidentityId doesn't match an identity
Email address for contact notification is invalidMalformed contact.notification.value for email
Phone number for contact notification must use international E.164 formatMalformed contact.notification.value for SMS
Email address for contact verification is invalidAs above, legacy contact.verification
Phone number for contact verification must use international E.164 formatAs above, legacy contact.verification

Verification methods (verificationMethods)

ErrorCause
At least one verification method is requiredEmpty array supplied
Duplicate verification method: <type>Same type listed twice
OTP payload (channel and value) is required when type is otpotp object omitted
Email address for OTP verification method is invalidMalformed OTP email destination
Phone number for OTP verification method must use international E.164 formatMalformed OTP SMS destination
IDV payload (givenName, surname) is required when type is idvTrinsicidvTrinsic object omitted
idvTrinsic.givenName must not be blank / idvTrinsic.surname must not be blankWhitespace-only names
idvTrinsic must be combined with otp or conciergeSignin: IDV alone has no redemption pathIDV specified on its own
requiresIdentityVerification (v1) is no longer supported for IDV. Use verificationMethods with type idvTrinsic (combined with otp or conciergeSignin) instead.Legacy IDV flag used

Photo, face check and expiry

ErrorCause
Face check photo or using a photo capture request is required for issuance of this contractContract requires face check; neither faceCheckPhoto nor photoCapture supplied
Contract must support face check when providing either a face check photo or using a photo capture requestContract doesn't support face check but a photo was supplied
Face check photo cannot be provided when using a photo capture requestBoth faceCheckPhoto and photoCapture supplied
Face check photo must be a valid image/jpeg data URL with base64 encodingPhoto isn't a base64 image/jpeg data URL
Credential expiry must fall after the period to claim itexpirationDate falls before the expiry claim window ends

Payload size

ErrorCause
Issuance request payload size exceeds the limit of 1 MBSingle request payload over 1 MB (Microsoft Entra Verified ID limit)
An unknown error occurredFallback for a non-Error throwable — please report this if encountered

Reference: whole-call errors

ErrorCause
Not Authorized! (code FORBIDDEN, HTTP 403)Token lacks issuer permission — this mutation requires an issuer user role or an issuance application registration
Cannot create more than 1000 async issuance requests at onceHard batch cap; use a batching strategy
Identity verification (IDV) is not supported in batch issuance requestsIDV requested with more than one item in the batch — IDV is single-request only
Trinsic IDV feature is not availableIDV not enabled on your instance
Face check feature is not availableFace check not enabled on your instance
Concierge sign-in verification requires the recipient identity to already hold an active credentialconciergeSignin used for a recipient with no active credential
Identity or identity ID must be provided / Identity not foundNeither identityId nor identity supplied, or the identity couldn't be resolved
GraphQL schema/scalar errors (code BAD_USER_INPUT, HTTP 400)Malformed UUID, DateTime, URL or JSONObject values, or givenName/surname outside 1–255 characters. Returned by the GraphQL layer before this mutation runs

Remote issuance completion process

Once the remote issuance is created, if notification details are set, the notification email or SMS will be sent to the recipient including a link to complete the issuance.

The issuance link can be opened on a desktop or on a mobile device. Regardless of the method, the recipient will need a mobile device with a wallet or Microsoft Authenticator installed to complete the issuance.

  • The recipient opens the issuance link.
  • They are prompted either to enter a one-time passcode (OTP) or to sign in to the Concierge.
  • If photo capture was requested at time of creation, the recipient will be guided to capture a photo.
  • If using a mobile device, the recipient will be prompted to open Microsoft Authenticator.
    • If the recipient does not have Microsoft Authenticator installed, they will be prompted to install it.
  • Otherwise, the recipient will be prompted to scan the QR code with their mobile device.
  • The flow ends with the recipient adding the issued credential to their wallet.
note

A verifiable credential with Face Check requires presentation via the Microsoft Authenticator app and will not work with other wallet apps.

Administration of remote issuances

Once the remote issuance is created, it can be viewed and managed via the Composer.

  • The Remote Issuances page lists all remote issuances, with the ability to filter by status, recipient, credential and date of creation.
  • Pending remote issuances can be cancelled by selecting one or more in the list, or from the details page when viewing a single remote issuance.
  • The details page shows a communications log, showing issuance and verification communications sent to the recipient. Communication failures are also shown, including the error encountered to assist with troubleshooting.
  • The recipient's contact details can be revealed and checked for correctness.
  • If required, contact details can be corrected.
  • The remote issuance notification can be resent to the recipient.

Testing integration

It's often useful to test the remote issuance flow during integration with your application. However, during testing, you may not want to send real SMS or email notifications to recipients.

Testing with email

The following domains can be used to generate email addresses for testing purposes. These domains will not send emails to real recipients:

  • @example.com
  • @example.org
  • @example.net
  • @example.edu

Example email addresses that will be ignored: sally.tester@exmple.com, fredrick.tester@example.edu.

Testing with SMS

The following phone numbers can be used for testing purposes. These numbers will not send SMS messages to real recipients:

Australia:

  • +61491570006
  • +61491570157
  • +61491570737
  • +61491573087
  • +61491578957

United States:

  • +15005550109
  • +15005550119
  • +15005550129
  • +15005550139
  • +15005550149

United Kingdom:

  • +447709000018
  • +447709000028
  • +447709000038
  • +447709000048
  • +447709000058