Skip to main content

Issue a credential

The credential issuer credential endpoint is a required endpoint defined in the OID4VCI specification. It’s where GOV.UK Wallet, acting on behalf of the holder, requests and receives verifiable credentials from the credential issuer. As a credential issuer, you must implement this endpoint according to this specification to correctly integrate with GOV.UK Wallet.

Technical details

Endpoint location

The credential endpoint’s URI path is implementation-specific.

As the credential issuer, you must publish the location of your credential endpoint in your issuer metadata API using the credential_endpoint parameter.

Request format

Your credential endpoint must accept HTTP POST requests.

GOV.UK Wallet will send a request to your credential endpoint to get a verifiable credential. The credential request must include the:

  • Authorization header: a bearer access token, which must be a JSON Web Token (JWT), issued by GOV.UK One Login
  • JSON request body: a proof of possession of the cryptographic key material, in the form of a JWT, issued by GOV.UK Wallet — this proves the wallet controls the private key to which the credential will be bound

Request validation

Authorization header

The access token in the credential request authorises the issuance of a verifiable credential to GOV.UK Wallet. It’s different from the access token used when the user initially logs in to GOV.UK One Login, which is for authentication purposes. Because they have different roles, the access tokens are signed and verified using different keys.

You must validate the access token to make sure it was issued by GOV.UK One Login, and that the request originates from the expected user.

To validate the access token, you must complete the following steps:

Verify the signature
  1. Retrieve the GOV.UK One Login JSON Web Key Set (JWKS) from its published JWKS endpoint.
  2. Copy the kid (key ID) parameter from the header.
  3. Find the matching public key in the JWKS by comparing kid values.
  4. Confirm the alg (algorithm) parameter in the token header matches the algorithm of the identified public key.
  5. Use the matching public key to cryptographically verify the token signature using the specified algorithm.
Validate the header parameters

Make sure the value of the typ (type) parameter is at+jwt.

This is an example of an access token header:

{
  "alg": "ES256",
  "typ": "at+jwt",
  "kid": "8f9ec544-f5df-4d37-a32d-a5defd78ab0f"
}
Validate the payload claims

Make sure that:

  • the value of the iss (issuer) claim matches the GOV.UK One Login URL: https://token.account.gov.uk for production or https://token.integration.account.gov.uk for integration
  • the value of the aud (audience) claim is the credential issuer URL
  • the value of the sub (subject - this is the wallet subject identifier) claim matches the value stored in your cache for this specific credential issuance flow
  • the value of the exp (expiration time) claim is in the future
  • the value of the credential_identifiers claim matches the value stored in your cache for this specific credential issuance flow
  • the value of the c_nonce claim matches the value of the nonce claim in the proof of possession
  • the value of the jti (JWT ID) claim is unique among all non-expired access tokens received by this API

This is an example of an access token payload:

{
  "sub": "urn:fdc:wallet.account.gov.uk:2024:DtPT8x-dp_73tnlY3KNTiCitziN9GEherD16bqxNt9i",
  "iss": "https://token.account.gov.uk",
  "aud": "https://example-credential-issuer.gov.uk",
  "exp": 1756115975,
  "credential_identifiers": [
    "daa01d3e-b17c-4c8a-8adf-ef808b456c9c"
  ],
  "c_nonce": "657a09cd-7165-486d-a858-065eb23f7a8d",
  "jti": "62b45850-4c5c-4696-983a-af66450301d4"
}

The sub claim is a pairwise identifier generated by GOV.UK One Login. Your implementation must compare this sub claim against the wallet subject identifier you stored when the user authenticated with GOV.UK One Login. This comparison makes sure that the wallet requesting the credential belongs to the same user which authenticated with your service. This identifier starts with urn:fdc:wallet.account.gov.uk:.

Warning If the identifiers do not match, the wallet trying to get the credential does not belong to the person logged in to your service. As a credential issuer you must stop the issuance flow and consider logging the attempt for audit and fraud prevention.

Request body

The request body sent by GOV.UK Wallet to your credential endpoint must be a JSON object containing proof of possession that demonstrates the wallet’s control of the private key to which the credential will be bound.

It must contain the following parameters:

Parameter Description Value(s)
proof A JSON containing the proof of possession of the cryptographic key material.
proof.proof_type A string indicating the type of proof being presented. Must be jwt.
proof.jwt The JSON Web Token (JWT) that serves as the proof.

Below is an example of a request body:

{
   "proof":{
      "proof_type":"jwt",
      "jwt":"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImRpZDprZXk6ekRuYWVTR2ZTUU1Zdm5MYkxXRXViaGhHRFBvcTdwQTlNTU52dW12YnNtTUNab3ZVUiJ9.eyJpc3MiOiJ1cm46ZmRjOmdvdjp1azp3YWxsZXQiLCJhdWQiOiJodHRwczovL2V4YW1wbGUtY3JlZGVudGlhbC1pc3N1ZXIuZ292LnVrIiwiaWF0IjoxNzQ1MjMzNjIzODE2LCJub25jZSI6IjY1N2EwOWNkLTcxNjUtNDg2ZC1hODU4LTA2NWViMjNmN2E4ZCJ9.UNszjzQeT6Vv8-n5kEAoHIr84Tf2gCJxMzhiUBBPPHdv6l0JK3WzNVaV0V6wgkTccLozOa7y3_3lp2iM4KjOOw"
   }
}

The proof of possession is a cryptographic mechanism that verifies the wallet controls the private key that matches the public key (did:key) that will be associated with the credential. This ensures credentials are issued to their rightful holder.

GOV.UK Wallet generates this token and includes a cryptographic client nonce (from the access token issued by GOV.UK One Login) that has been signed with the wallet’s private signing key. The did:key (the wallet’s public key) is included in the token’s header kid parameter.

When you receive the credential request, you can verify the proof of possession signature with the did:key. Successful verification shows the wallet’s ownership of the private key corresponding to that public did:key.

There is more information about the did:key method.

To validate the proof, you must complete the following steps:

Verify the signature
  1. Extract the kid (key ID) parameter from the header, which contains the wallet’s did:key.
  2. Extract the public key from the did:key.
  3. Confirm the alg (algorithm) parameter in the proof header is ES256 and is compatible with the key type derived from the did:key.
  4. Use the public key to cryptographically verify the proof signature using the specified algorithm.
Validate the header parameters

Make sure the value of the typ (type) parameter is openid4vci-proof+jwt.

This is an example of a proof header:

{
  "alg": "ES256",
  "typ": "openid4vci-proof+jwt",
  "kid": "did:key:zDnaeSGfSQMYvnLbLWEubhhGDPoq7pA9MMNvumvbsmMCZovUR"
}
Validate the payload claims

Make sure the:

  • value of the iss (issuer) claim matches the GOV.UK Wallet identifier - urn:fdc:gov:uk:wallet
  • value of the aud (audience) claim is the credential issuer URL
  • value of the iat (issued at) is a numeric date formatted as seconds since the epoch - this must be a date in the past that is after the pre-authorised code was generated
  • value of the nonce claim matches the value of the c_nonce claim in the access token

This is an example of a proof payload:

{
  "iss": "urn:fdc:gov:uk:wallet",
  "aud": "https://example-credential-issuer.gov.uk",
  "iat": 1745233623816,
  "nonce": "bd423745-7705-45c2-9f51-6ae8dcac5589"
}

You can find more information about the credential request in the OID4VCI specification.

Response format

After validating the request successfully, your credential endpoint must return a 200 OK HTTP status code and a JSON response following the OID4VCI specification.

Below is an example of a validated credential response:

{
  "credentials": [
    {
      "credential": "eyJraWQiOiJkaWQ6d2ViOmV4YW1wbGUtY3JlZGVudGlhbC1pc3N1ZXIuZ292LnVrIzVkY2JlZTg2M2I1ZDdjYzMwYzliYTFmNzM5M2RhY2M2YzE2NjEwNzgyZTRiNmExOTFmOTRhN2U4YjFlMTUxMGYiLCJjdHkiOiJ2YyIsInR5cCI6InZjK2p3dCIsImFsZyI6IkVTMjU2In0.eyJpc3MiOiJodHRwczovL2V4YW1wbGUtY3JlZGVudGlhbC1pc3N1ZXIuZ292LnVrIiwic3ViIjoiZGlkOmtleTp6RG5hZVNHZlNRTVl2bkxiTFdFdWJoaEdEUG9xN3BBOU1NTnZ1bXZic21NQ1pvdlVSIiwiaWF0IjoiMTcxMjY2NDczMSIsIm5iZiI6IjE3MTI2NjQ3MzEiLCJleHAiOiIxNzQ0MjIxNjU3IiwiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnL25zL2NyZWRlbnRpYWxzL3YyIiwiPEpTT04tTEQgQ09OVEVYVCBVUkkgRk9SIElTU1VFUj4iXSwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIkZpc2hpbmdMaWNlbmNlIl0sImlzc3VlciI6Imh0dHBzOi8vZXhhbXBsZS1jcmVkZW50aWFsLWlzc3Vlci5nb3YudWsiLCJuYW1lIjoiRmlzaGluZyBsaWNlbmNlIiwiZGVzY3JpcHRpb24iOiI8T1BUSU9OQUwgQ1JFREVOVElBTCBERVNDUklQVElPTj4iLCJ2YWxpZEZyb20iOiIyMDI0LTA0LTA5VDEyOjEyOjExWiIsInZhbGlkVW50aWwiOiIyMDI4LTEyLTEwVDIyOjU5OjU5WiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmtleTp6RG5hZVNHZlNRTVl2bkxiTFdFdWJoaEdEUG9xN3BBOU1NTnZ1bXZic21NQ1pvdlVSIiwibmFtZSI6W3sibmFtZVBhcnRzIjpbeyJ2YWx1ZSI6IlNhcmFoIiwidHlwZSI6IkdpdmVuTmFtZSJ9LHsidmFsdWUiOiJFZHdhcmRzIiwidHlwZSI6IkZhbWlseU5hbWUifV19XSwiZmlzaGluZ0xpY2VuY2UiOlt7ImxpY2VuY2VOdW1iZXIiOiIwMDk4Nzg4NjMiLCJpc3N1YW5jZURhdGUiOiIyMDIzLTEyLTEwIiwiZXhwaXJhdGlvbkRhdGUiOiIyMDI4LTEyLTEwIn1dfX0.UXGyl9ihcMNn-RlqBe_8rTxvtPDqdaGLKMJs3pk9QL4zgEZbF4LZmeZt-k2p04MSlW8IX8uWKgtxFDOGu9j2rQ"
    }
  ],
  "notification_id": "776aefd4-26c6-4a5f-aa7c-b5e294cd87cd"
}

You can find more information about the credential response in the OID4VCI specification. You should only include the notification_id in the response if you implement the notification endpoint for the callback success/failure notification.

To construct and issue the verifiable credential:

  1. Retrieve the underlying data that will go into the credential from your database using the information in your issuance cache.
  2. Build the verifiable credential according to the W3C Verifiable Credentials Data Model v2.0.
  3. Cryptographically bind the credential to the wallet by using the wallet’s did:key as the credential’s subject identifier (the sub claim).
  4. Sign the credential with your issuer’s private key.

The header and payload example below shows the structure of a verifiable credential using a JSON Web Token (JWT) format for a fishing licence.

{
  "alg": "ES256",
  "typ": "vc+jwt",
  "cty": "vc",
  "kid": "did:web:example-credential-issuer.gov.uk#5dcbee863b5d7cc30c9ba1f7393dacc6c16610782e4b6a191f94a7e8b1e1510f"
}
Parameter Required or optional Definition
alg Required algorithm. The cryptographic algorithm used to sign the JWT - must be ES256 for ECDSA using the P-256 curve.
cty Required content type. The media type of the secured content (the payload) - must be vc.
typ Required type. The media type of the signed content - must be vc+jwt.
kid Required key ID. The Decentralised Identifier (DID) URL of the issuer’s public key used for signature verification - this must match the id of the corresponding key in the credential issuer’s DID Document.

Payload

{
  "iss": "https://example-credential-issuer.gov.uk",
  "sub": "did:key:zDnaeSGfSQMYvnLbLWEubhhGDPoq7pA9MMNvumvbsmMCZovUR",
  "nbf": "1712664731",
  "iat": "1712664731",
  "exp": "1744221657",
  "@context": [
    "https://www.w3.org/ns/credentials/v2",
    "<JSON_LD_CONTEXT_URI_FOR_ISSUER>"
  ],
  "type": [
    "VerifiableCredential",
    "FishingLicenceCredential"
  ],
  "issuer": "https://example-credential-issuer.gov.uk",
  "name": "Fishing licence",
  "description": "Permit for fishing activities",
  "validFrom": "2024-04-09T12:12:11Z",
  "validUntil": "2026-04-09T22:59:59Z",
  "credentialSubject": {
    "id": "did:key:zDnaeSGfSQMYvnLbLWEubhhGDPoq7pA9MMNvumvbsmMCZovUR",
    "name": [
      {
        "nameParts": [
          {
            "value": "Sarah",
            "type": "GivenName"
          },
          {
            "value": "Edwards",
            "type": "FamilyName"
          }
        ]
      }
    ],
    "fishingLicenceRecord": [
      {
        "licenceNumber": "009878863",
        "issuanceDate": "2023-12-10",
        "expiryDate": "2028-12-10"
      }
    ]
  }
}
Parameter Required or optional Definition
@context Required The context of the data exchange. This must be a set of URIs that point to documents that describe the context. The first item in the set must be the URI https://www.w3.org/ns/credentials/v2.
credentialSubject Required An object containing claims about the holder of the verifiable credential.
iss Required Issuer. The URL of the credential issuer service operated by the organisation sharing the credential.
issuer Required The URL of the credential issuer service operated by the organisation sharing the credential. Must be the same as the value of the iss claim.
sub Required Subject. The identifier of the holder of the information in the credential. The subject identifier is a decentralised identifier did:key generated by the wallet. In the credential issuance flow, the wallet shares its did:key with the issuer, and the issuer makes this the value of the credential’s sub claim. This cryptographically binds the credential to the wallet.
type Required A set of values indicating the verifiable credential type. The first value in the set must be VerifiableCredential.
validUntil Required The date and time the credential stops being valid. This value specifies the date until which the information within the credentialSubject property remains valid. In the example above, the values of expiryDate and validUntil are the same. Must be expressed in ISO 8601 format with seconds (YYYY-MM-DDTHH:mm:ssZ) as per the VC data model v2.0.
description Optional Issuer-specified credential description.
iat Optional Issued at. The time the JWT was issued. Must be expressed in epoch time as per the IETF RFC 7519.
name Optional Issuer-specified credential name.
validFrom Optional The date and time the credential becomes valid. Must be expressed in ISO 8601 format with seconds (YYYY-MM-DDTHH:mm:ssZ) as per the VC data model v2.0.
Guidance on credential expiration

Verifiable credentials issued to GOV.UK Wallet have two expiry dates:

  • the technical expiry date, set using the validUntil claim, is when the digital version of the document is no longer valid.
  • the document expiry date, specified using the expiryDate claim, is when the physical document expires and must be renewed.

In the example above, a user has a valid fishing licence. This physical document owned by the user expires on 2028-12-10, as set by its credentialSubject.fishingLicenceRecord.expiryDate. The verifiable credential version of this credential issued to GOV.UK Wallet is only valid until 2026-04-09T22:59:59Z, as set by its validUntil claim. On 09/04/2026, this user must refresh the digital version of their fishing licence in the app. On 10/12/2028, the user must renew their fishing licence with the department who issued it.

Credential consumers must consider any verifiable credential as invalid (expired) if the validUntil date has passed, regardless of the credentialSubject.fishingLicenceRecord.expiryDate. Any expired credentials must be rejected by the consumer.

Guidance on including photos

If a photo is required in the credential, you must include it within the credentialSubject claim as a Base64-encoded string.

GOV.UK Wallet will validate the image to make sure that:

  • its format is JPG or PNG conforming to one of the following file specifications:

    • FF D8 FF E0 JPG
    • FF D8 FF EE JPG
    • FF D8 FF DB JPG
    • 89 50 4E 47 0D 0A 1A 0A PNG
    • FF D8 FF E0 00 10 4A 46 49 46 00 01 JFIF
  • it does not exceed 1 MiB (1 Mebibyte = 1,048,576 bytes) in size before encoding to Base64

  • its EXIF metadata has been stripped

If GOV.UK Wallet fails to process an image in a credential, a credential_failure notification will be sent, following the notification specification.

This is an example of a claim representing a PNG image, encoded in Base64 format:

"photo": "iVBORw0KGgoAAAANSUhIAAAAE2BViAAAA[...]" // PNG file as source

This is an example of a claim representing a JPEG image, encoded in Base64 format:

"photo": "/9j/4AAQSkZJRgABAQpDYXQwMy5qcGf/[...]" // JPG file as source

Signature

The credential must be signed with your credential issuer’s private signing key using the ECDSA (Elliptic Curve Digital Signature Algorithm) cryptographic algorithm with P-256 (also known as Secp256r1) elliptic curve.

Error response format

If the credential request could not be processed successfully, the credential issuer must return an appropriate HTTP error status code.

When the credential request does not include an access token or the access token is invalid, your credential endpoint must return a 401 Unauthorized HTTP status code and include the WWW-Authenticate response header field as defined in RFC6750, specifying the Bearer authentication scheme.

This is an example error response to a request with no access token:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer
Cache-Control: no-store

This is an example error response to an authentication attempt using an invalid access token:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="invalid_token"
Cache-Control: no-store

When there is an issue with the request body, your credential endpoint must return a 400 Bad Request HTTP status code and a response body in JSON format containing the following parameter:

  • error: a case-sensitive string indicating the error - invalid_proof (the proof of possession is invalid), or invalid_nonce (the nonce is invalid or does not match the access token’s c_nonce)

Below is an example of a credential error response when the credential request included an invalid proof of possession:

HTTP/1.1 400 Bad Request
Content-Type: application/json
Cache-Control: no-store

{
  "error": "invalid_proof"
}

Further guidance on credential binding

Binding credentials to users

Because each GOV.UK Wallet instance can be uniquely identified, credentials are bound to a specific instance. GOV.UK Wallet uses a specific type of decentralised identifier (DID) called a did:key to cryptographically bind credentials.

A did:key is a DID method. The DID represents the public key of an asymmetric key pair generated when GOV.UK Wallet is installed on a device. The private key never leaves the device, whereas the did:key is shared with credential issuers and verifiers. This allows credentials to be cryptographically bound to a specific GOV.UK Wallet instance.

GOV.UK Wallet creates a did:key from a P-256 (also known as Secp256r1) elliptic curve public key.

Using the did:key format

You can use the did:key method to transfer public keys. The format of a did:key is:

did:key:<MULTIBASE_VALUE>

The <MULTIBASE_VALUE> is a multicodec identifier for the public key type followed by the compressed public key, encoded as a base58-btc multibase string. To generate this value:

  1. Encode the compressed public key as bytes.
  2. Prefix the key bytes with the curve multicodec value encoded as unsigned varint (variable length integers). The multicodec hexadecimal value of a P-256 elliptic curve public key is 0x1200; in varint-encoded bytes that is [0x80, 0x24].
  3. Encode the above with base58-btc and then prefix it with z to indicate the base58-btc encoding.

This is an example of a valid P-256 did:key:

did:key:zDnaeSGfSQMYvnLbLWEubhhGDPoq7pA9MMNvumvbsmMCZovUR

DIDs generated from a P-256 public key always start with zDn.

This page was last reviewed on 8 December 2025. It needs to be reviewed again on 8 June 2026 .