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. Government departments acting as credential issuers must implement this endpoint according to this specification to correctly integrate with GOV.UK Wallet.
Technical details
Endpoint URI
The credential endpoint’s URI path is implementation-specific.
The credential issuer must publish the location of their credential endpoint in their issuer metadata API using the credential_endpoint
parameter.
Request format
The credential endpoint must accept HTTP POST requests.
GOV.UK Wallet will send a request to the credential issuer’s credential endpoint to get a verifiable credential. The credential request must include:
- the Authorization header: a bearer access token (JWT) issued by GOV.UK One Login — this token authorises the credential issuance
- the request body (JSON): a proof of possession token (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 is used to authorise the issuance of a verifiable credential. 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, they are signed and verified using different keys.
The credential issuer must validate the access token to make sure that it was issued by GOV.UK One Login and that the request originates from the expected user.
To validate the access token, you should complete the following steps.
1. Verify the signature:
- retrieve the GOV.UK One Login JSON Web Key Set (JWKS) from its published JWKS endpoint
- extract the
kid
(key ID) parameter from the header - find the matching public key in the JWKS by comparing
kid
values - confirm the
alg
(algorithm) parameter in the token header matches the algorithm of the identified public key - use the matching public key to cryptographically verify the token signature using the specified algorithm
2. Validate the header parameters by ensuring that:
- the value of the
typ
(Type) parameter is"at+jwt"
Below is an example of an access token header:
{
"alg": "ES256",
"typ": "at+jwt",
"kid": "8f9ec544-f5df-4d37-a32d-a5defd78ab0f"
}
3. Validate the payload claims by ensuring that:
- the value of the
iss
(issuer) claim matches the GOV.UK One Login URL:"https://token.integration.account.gov.uk"
(integration) or"https://token.account.gov.uk"
(production) - 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
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 thenonce
claim in the proof of possession - this API has not received another access token with the same
jti
(JWT ID) that is still within its validity period
Below 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",
"credential_identifiers": [
"daa01d3e-b17c-4c8a-8adf-ef808b456c9c"
],
"c_nonce": "657a09cd-7165-486d-a858-065eb23f7a8d",
"jti": "62b45850-4c5c-4696-983a-af66450301d4"
}
The sub
claim validation is an important security control. This pairwise identifier, which starts with urn:fdc:wallet.account.gov.uk:
, is generated by GOV.UK One Login for each user’s wallet instance and helps ensure the credential is issued to the right user.
Your implementation must compare the sub
value from the access token against the wallet subject identifier you got and 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 who authenticated with your service.
Request body
The request body sent to the 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.
This token is generated by GOV.UK Wallet 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 your credential issuer receives the credential request, it verifies 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 should complete the following steps.
1. Verify the signature:
- extract the
kid
(key ID) parameter from the header, which contains the wallet’sdid:key
- convert the
did:key
value to its corresponding public key - confirm the
alg
(algorithm) parameter in the proof header isES256
and is compatible with the key type derived from thedid:key
- use the public key to cryptographically verify the proof signature using the specified algorithm
2. Validate the header parameters by ensuring that:
- the value of the
typ
(Type) parameter is"jwt"
Below is an example of a proof header:
{
"alg": "ES256",
"typ": "jwt",
"kid": "did:key:zDnaeSGfSQMYvnLbLWEubhhGDPoq7pA9MMNvumvbsmMCZovUR"
}
3. Validate the payload claims by ensuring that:
- the value of the
iss
(issuer) claim matches the GOV.UK Wallet identifier -urn:fdc:gov:uk:wallet
- the value of the
aud
(audience) claim is the credential issuer URL - the value of the
iat
(issued at) claim matches the value stored in your cache for this specific credential issuance flow - the value of the
nonce
claim matches the value of thec_nonce
claim in the access token
Below is an example of a proof of possession token payload:
{
"iss": "urn:fdc:gov:uk:wallet",
"aud": "https://example-credential-issuer.gov.uk",
"iat": 1745233623816,
"nonce": "bd423745-7705-45c2-9f51-6ae8dcac5589"
}
More information about the credential request can be found in the OID4VCI specification.
Response format
After validating the request successfully, the credential endpoint must return a 200 OK HTTP status code and a JSON response following the OID4VCI specification. The response contains the issued credential as a JWT and a unique notification ID (a generated UUIDv4) for the callback success/failure notification:
{
"credentials": [
{
"credential": "eyJraWQiOiJkaWQ6d2ViOmV4YW1wbGUtY3JlZGVudGlhbC1pc3N1ZXIuZ292LnVrIzVkY2JlZTg2M2I1ZDdjYzMwYzliYTFmNzM5M2RhY2M2YzE2NjEwNzgyZTRiNmExOTFmOTRhN2U4YjFlMTUxMGYiLCJjdHkiOiJ2YyIsInR5cCI6InZjK2p3dCIsImFsZyI6IkVTMjU2In0.eyJpc3MiOiJodHRwczovL2V4YW1wbGUtY3JlZGVudGlhbC1pc3N1ZXIuZ292LnVrIiwic3ViIjoiZGlkOmtleTp6RG5hZVNHZlNRTVl2bkxiTFdFdWJoaEdEUG9xN3BBOU1NTnZ1bXZic21NQ1pvdlVSIiwiaWF0IjoiMTcxMjY2NDczMSIsIm5iZiI6IjE3MTI2NjQ3MzEiLCJleHAiOiIxNzQ0MjIxNjU3IiwiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnL25zL2NyZWRlbnRpYWxzL3YyIiwiPEpTT04tTEQgQ09OVEVYVCBVUkkgRk9SIElTU1VFUj4iXSwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIkZpc2hpbmdMaWNlbmNlIl0sImlzc3VlciI6Imh0dHBzOi8vZXhhbXBsZS1jcmVkZW50aWFsLWlzc3Vlci5nb3YudWsiLCJuYW1lIjoiRmlzaGluZyBsaWNlbmNlIiwiZGVzY3JpcHRpb24iOiI8T1BUSU9OQUwgQ1JFREVOVElBTCBERVNDUklQVElPTj4iLCJ2YWxpZEZyb20iOiIyMDI0LTA0LTA5VDEyOjEyOjExWiIsInZhbGlkVW50aWwiOiIyMDI4LTEyLTEwVDIyOjU5OjU5WiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmtleTp6RG5hZVNHZlNRTVl2bkxiTFdFdWJoaEdEUG9xN3BBOU1NTnZ1bXZic21NQ1pvdlVSIiwibmFtZSI6W3sibmFtZVBhcnRzIjpbeyJ2YWx1ZSI6IlNhcmFoIiwidHlwZSI6IkdpdmVuTmFtZSJ9LHsidmFsdWUiOiJFZHdhcmRzIiwidHlwZSI6IkZhbWlseU5hbWUifV19XSwiZmlzaGluZ0xpY2VuY2UiOlt7ImxpY2VuY2VOdW1iZXIiOiIwMDk4Nzg4NjMiLCJpc3N1YW5jZURhdGUiOiIyMDIzLTEyLTEwIiwiZXhwaXJhdGlvbkRhdGUiOiIyMDI4LTEyLTEwIn1dfX0.UXGyl9ihcMNn-RlqBe_8rTxvtPDqdaGLKMJs3pk9QL4zgEZbF4LZmeZt-k2p04MSlW8IX8uWKgtxFDOGu9j2rQ"
}
],
"notification_id": "776aefd4-26c6-4a5f-aa7c-b5e294cd87cd"
}
The notification_id
should only be included in the response if the credential issuer implements the notification endpoint.
More information about the credential response can be found in the OID4VCI specification.
The steps for constructing and issuing the verifiable credential are:
- Retrieve the underlying data that will go into the credential from your database using the information in your issuance cache.
- Build the verifiable credential according to the W3C Verifiable Credentials Data Model v2.0.
- Cryptographically bind the credential to the wallet by using the wallet’s
did:key
as the credential’s subject identifier (thesub
claim). - Sign the credential with your issuer’s private key.
The example below shows the structure of a verifiable credential using a JSON Web Token (JWT) format for a fishing licence.
Header
{
"alg": "ES256",
"typ": "vc+jwt",
"cty": "vc",
"kid": "did:web:example-credential-issuer.gov.uk#5dcbee863b5d7cc30c9ba1f7393dacc6c16610782e4b6a191f94a7e8b1e1510f"
}
alg
(algorithm). REQUIRED. The cryptographic algorithm used to sign the JWT - must be"ES256"
for ECDSA using the P-256 curve.typ
(type). REQUIRED. The media type of the signed content - must be"vc+jwt
.cty
(content type). REQUIRED. The media type of the secured content (the payload) - must be"vc"
.kid
(key ID). REQUIRED. The DID URL of the issuer’s public key used for signature verification - must match theid
of the corresponding key in the credential issuer’s DID Document, to let recipients locate the correct public key for signature verification.
Payload
{
"iss": "https://example-credential-issuer.gov.uk",
"sub": "did:key:ebfaeb1fd712ebf1c276e12ec21",
"nbf": "1712664731",
"iat": "1712664731",
"exp": "1744221657",
"@context": [
"https://www.w3.org/ns/credentials/v2",
"<JSON-LD CONTEXT URI FOR ISSUER>"
],
"type": [
"VerifiableCredential",
"FishingLicence"
],
"issuer": "https://example-credential-issuer.gov.uk",
"name": "Fishing licence",
"description": "<OPTIONAL CREDENTIAL DESCRIPTION>",
"validFrom": "2024-04-09T12:12:11Z",
"validUntil": "2028-12-10T22:59:59Z",
"credentialSubject": {
"id": "did:key:ebfaeb1fd712ebf1c276e12ec21",
"name": [
{
"nameParts": [
{
"value": "Sarah",
"type": "GivenName"
},
{
"value": "Edwards",
"type": "FamilyName"
}
]
}
],
"fishingLicenceRecord": [
{
"licenceNumber": "009878863",
"issuanceDate": "2023-12-10",
"expirationDate": "2028-12-10"
}
]
}
}
iss
(issuer). REQUIRED. The URL of the credential issuer service operated by the organisation sharing the credential.sub
(subject). REQUIRED. The identifier of the holder of the information in the credential. The subject identifier is a decentralised identifierdid:key
generated by the wallet. In the credential issuance flow, the wallet shares itsdid:key
with the issuer, and the issuer makes this the value of the credential’ssub
claim. This cryptographically binds the credential to the wallet.nbf
(not before). REQUIRED. The time before which the JWT must not be accepted for processing. Must be expressed in epoch time as per the IETF RFC 7519.iat
(issued at). OPTIONAL. The time at which the JWT was issued. Must be expressed in epoch time as per the IETF RFC 7519.exp
(expiration time). OPTIONAL. The time after which the JWT must not be accepted for processing. Must be expressed in epoch time as per the IETF RFC 7519. It is mandatory to have at least one ofexp
orvalidUntil
claims.@context
. REQUIRED. The context of the data exchange. It 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"
.type
. REQUIRED. A set of values indicating the type of verifiable credentials issued by the issuer. The first value in the set must beVerifiableCredential
issuer
. REQUIRED. The URL of the credential issuer service operated by the organisation sharing the credential. Must be the same as the value of theiss
claim.name
. OPTIONAL. Issuer-specified credential name.description
. OPTIONAL. Issuer-specified credential description.validFrom
. OPTIONAL. It represents the date and time the credential becomes valid, and the same as the value of thenbf
/iat
claims. Must be expressed in ISO 8601 format as per the VC data model v2.0.validUntil
. OPTIONAL. It represents the date and time the credential stops being valid. This value specifies the date until which the information within thecredentialSubject
property remains valid. In the example above, the values ofexpirationDate
andvalidUntil
are the same. Must be expressed in ISO 8601 format as per the VC data model v2.0. It is mandatory to have at least one ofexp
orvalidUntil
claims.credentialSubject
. REQUIRED. An object containing claims about the holder of the verifiable credential. For credentials that include a photo, the photo size must be no larger than 1MiB and must be JPEG or PNG
Guidance on credential expiration
The validUntil
claim in the credential means the same as the JWT-standard exp
claim - it represents the date after which any consumer of the verifiable credential must reject it as not valid. The entitlement in the underlying data source (the fishing license record in the example) may still be valid, as represented by the expirationDate
. If the validUntil
date has passed, the holder must get a new verifiable credential to update the digital representation of their entitlement.
GOV.UK Wallet accepts both validUntil
and exp
, but validUntil
is preferred. validUntil
or exp
must be set to the same date as, or earlier than, the expirationDate
. If both validUntil
and exp
are provided, GOV.UK Wallet will set the credential expiry to be the earlier of the two dates.
Always consider the expiration of the underlying credential when setting JWT expiration.
Guidance on including photos
If a photo is required in the credential, include it within the credentialSubject
claim as a Base64-encoded string. The image must be in JPEG or PNG format, and the encoded data URL must not exceed 1MiB. The data URL should follow the format data:[<mediatype>][;base64],<data>
. For example, a JPEG image would be represented as data:image/jpeg;base64,<base64-encoded-image-data>
.
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.
Further guidance on credential binding
Binding credentials to users
Because each GOV.UK Wallet instance can be uniquely identified, your service can bind a credential with a specific wallet instance. GOV.UK Wallet uses a specific type of decentralised identifier (DID) called a did:key
to cryptographically bind credentials to a user’s wallet.
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.
The GOV.UK Wallet creates a did:key
from a P-256 (also known as Secp256r1) elliptic curve public key.
The did:key
format
The did:key
method is used to transfer public keys.
The format of a did:key
is did:key:multibaseValue
. The multibaseValue
is the base58-btc multibase string representation of concatenating the multicodec identifier for the public key type and the compressed public key.
did-key-format := did:key:MULTIBASE(base58-btc, MULTICODEC(public-key-type, raw-public-key-bytes))
In Elliptic Curve Cryptography (ECC), the public key is a pair of x
and y
coordinates. A compressed public key is the x
coordinate, which is 32 bytes in length, with a prefix, of 1 byte in length, that indicates whether the y
coordinate is even or odd. The prefix is 02
if the y
coordinate is even and 03
if it is odd. The resulting compressed public key is 33 bytes in length:
Public key: 52972572d465d016d4c501887b8df303eee3ed602c056b1eb09260dfa0da0ab288742f4dc97d9edb6fd946babc002fdfb06f26caf117b9405ed79275763fdb1c
Public key (x coordinate): 52972572d465d016d4c501887b8df303eee3ed602c056b1eb09260dfa0da0ab2
Public key (y coordinate): 88742f4dc97d9edb6fd946babc002fdfb06f26caf117b9405ed79275763fdb1c
// y coordinate is even so "02" is prepended to the x coordinate
Public key (compressed): 0252972572d465d016d4c501887b8df303eee3ed602c056b1eb09260dfa0da0ab2
The multibaseValue
is generated as follows:
- Encode the compressed public key as bytes
- 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]
- Encode the above with base58-btc and then prefix it with
"z"
to indicate the base58-btc encoding - the result is themultibaseValue
The following is an example of a did:key
derived from a base-58 encoded P-256 public key:
did:key:zDnaewZMz7MN6xSaAFADkDZJzMLbGSV25uKHAeXaxnPCwZomX
All DIDs derived from a P-256 public key always start with "zDn"
.
Verifying a credential
To share a verifiable credential with a verifier, the wallet creates a verifiable presentation containing the verifiable credential and signs the credential with the wallet’s private key.
The verifier must be able to confirm that the system presenting the credential (GOV.UK Wallet) is also the intended holder of that credential. The verifier must confirm proof of possession of the verifiable credential. This is done by verifying that the entity which signed the verifiable presentation is the same as the subject of the verifiable credential. In other words, the did:key
in the verifiable credential must be able to verify the signature on the verifiable presentation.