Skip to content

Presentations

The Presentations endpoints allow you to create and verify PASETO v4 signed Verifiable Presentations for secure credential sharing between holders and verifiers.

Creates a signed PASETO v4 Verifiable Presentation containing one or more credentials.

POST /v1/presentations

Requires BearerAuth or ApiKeyAuth.

FieldTypeRequiredDescription
holder_didstringYesDID of the presentation holder
holder_private_keystringYesBase64-encoded private key for signing
credentialsarrayYesArray of PASETO v4 credential tokens to include
audiencestringYesDID of the intended verifier
noncestringNoChallenge nonce from the verifier
domainstringNoDomain for the presentation
Terminal window
curl -X POST "https://api.veriglob.com/v1/presentations" \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"holder_did": "did:key:z6MkholderDID...",
"holder_private_key": "base64-encoded-private-key",
"credentials": [
"v4.public.eyJpc3MiOi...",
"v4.public.eyJhbm90aGVy..."
],
"audience": "did:key:z6MkverifierDID...",
"nonce": "unique-challenge-nonce-12345",
"domain": "https://verifier.example.com"
}'

201 Created

{
"status": "success",
"message": "Presentation created successfully",
"data": {
"presentation_id": "urn:uuid:3978344f-8596-4c3a-a978-8fcaba3903c6",
"presentation": "v4.public.eyJwcmVzZW50YXRpb24iOnsiaG9sZGVyIjoiZGlkOmtleTo...",
"holder": "did:key:z6MkholderDID...",
"audience": "did:key:z6MkverifierDID...",
"credentials_count": 2,
"created_at": "2024-01-15T10:35:00Z"
}
}
StatusDescription
400Invalid request body, missing required fields, or invalid credentials
401Invalid or missing API key
429Rate limit exceeded

Verifies the presentation signature, audience, nonce, and optionally the contained credentials.

POST /v1/presentations/verify

Requires BearerAuth or ApiKeyAuth.

FieldTypeRequiredDescription
presentationstringYesThe PASETO v4 presentation token
audiencestringYesExpected audience DID (verifier’s DID)
noncestringNoExpected nonce value (if used during creation)
verify_credentialsbooleanNoWhether to verify contained credentials (default: true)
check_revocationbooleanNoWhether to check credential revocation status (default: false)
Terminal window
curl -X POST "https://api.veriglob.com/v1/presentations/verify" \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"presentation": "v4.public.eyJwcmVzZW50YXRpb24iOi...",
"audience": "did:key:z6MkverifierDID...",
"nonce": "unique-challenge-nonce-12345",
"verify_credentials": true,
"check_revocation": true
}'

200 OK (Valid)

{
"status": "success",
"message": "Presentation verified successfully",
"data": {
"valid": true,
"holder": "did:key:z6MkholderDID...",
"audience": "did:key:z6MkverifierDID...",
"nonce_valid": true,
"credentials": [
{
"valid": true,
"issuer": "did:key:z6MkissuerDID1...",
"subject": "did:key:z6MkholderDID...",
"credential_type": "EmploymentCredential",
"claims": {
"employer": "Acme Corporation",
"position": "Software Engineer"
},
"revoked": false,
"expired": false
},
{
"valid": true,
"issuer": "did:key:z6MkissuerDID2...",
"subject": "did:key:z6MkholderDID...",
"credential_type": "EducationCredential",
"claims": {
"institution": "State University",
"degree": "Bachelor of Science"
},
"revoked": false,
"expired": false
}
],
"created_at": "2024-01-15T10:35:00Z"
}
}

200 OK (Invalid)

{
"status": "success",
"message": "Presentation verification completed",
"data": {
"valid": false,
"error": "Audience mismatch",
"expected_audience": "did:key:z6MkverifierDID...",
"actual_audience": "did:key:z6MkotherDID..."
}
}
StatusDescription
400Invalid presentation format or missing fields
401Invalid or missing API key
429Rate limit exceeded
500Internal server error during verification

The typical flow for creating and verifying presentations:

┌─────────────┐ ┌─────────────┐
│ Holder │ │ Verifier │
└──────┬──────┘ └──────┬──────┘
│ │
│ 1. Request Presentation │
│◄─────────────────────────────────┤
│ (nonce, audience DID) │
│ │
│ 2. Create Presentation │
│─────────────────────────────────►│
│ (signed VP with credentials) │
│ │
│ 3. Verify Presentation
│ │
│ 4. Verification Result │
│◄─────────────────────────────────┤
│ │

The verifier generates a nonce and sends a presentation request:

{
"verifier_did": "did:key:z6MkverifierDID...",
"nonce": "unique-challenge-nonce-12345",
"requested_credentials": ["EmploymentCredential", "EducationCredential"]
}

The holder selects appropriate credentials and creates a signed presentation.

The verifier receives and verifies the presentation.


Always use a nonce to prevent replay attacks:

  1. Verifier generates a unique nonce for each presentation request
  2. Holder includes the nonce in the signed presentation
  3. Verifier validates the nonce matches the expected value
  4. Nonce should be single-use and time-limited
import { randomBytes } from 'crypto';
function generateNonce(): string {
return randomBytes(32).toString('base64url');
}
// Usage
const nonce = generateNonce();
// e.g., "dGhpcyBpcyBhIHNlY3VyZSByYW5kb20gbm9uY2U"

The audience claim ensures the presentation is intended for the specific verifier:

  • Always verify the audience matches your DID
  • Reject presentations with mismatched or missing audience
  • Don’t accept presentations intended for other verifiers

When verifying presentations, always:

  1. Verify the presentation signature - Confirms holder’s identity
  2. Verify each credential signature - Confirms issuer’s identity
  3. Check revocation status - Ensures credentials are still valid
  4. Validate expiration dates - Ensures credentials haven’t expired
  5. Verify credential subjects match holder - Ensures holder owns the credentials

import { randomBytes } from 'crypto';
const API_KEY = process.env.VERIGLOB_API_KEY!;
const VERIFIER_DID = 'did:key:z6MkverifierDID...';
// Step 1: Generate presentation request
function createPresentationRequest() {
const nonce = randomBytes(32).toString('base64url');
return {
verifier_did: VERIFIER_DID,
nonce,
requested_credentials: ['EmploymentCredential']
};
}
// Step 2: Verify received presentation
async function verifyReceivedPresentation(
presentation: string,
expectedNonce: string
) {
const response = await fetch('https://api.veriglob.com/v1/presentations/verify', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
presentation,
audience: VERIFIER_DID,
nonce: expectedNonce,
verify_credentials: true,
check_revocation: true
})
});
const result = await response.json();
if (!result.data.valid) {
throw new Error(`Verification failed: ${result.data.error}`);
}
// Additional business logic checks
const employmentCred = result.data.credentials?.find(
(c: any) => c.credential_type === 'EmploymentCredential'
);
if (!employmentCred) {
throw new Error('Required EmploymentCredential not found');
}
return {
holder: result.data.holder,
employer: employmentCred.claims.employer,
position: employmentCred.claims.position
};
}