Presentations
The Presentations endpoints allow you to create and verify PASETO v4 signed Verifiable Presentations for secure credential sharing between holders and verifiers.
Create a Presentation
Section titled “Create a Presentation”Creates a signed PASETO v4 Verifiable Presentation containing one or more credentials.
POST /v1/presentationsAuthentication
Section titled “Authentication”Requires BearerAuth or ApiKeyAuth.
Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
holder_did | string | Yes | DID of the presentation holder |
holder_private_key | string | Yes | Base64-encoded private key for signing |
credentials | array | Yes | Array of PASETO v4 credential tokens to include |
audience | string | Yes | DID of the intended verifier |
nonce | string | No | Challenge nonce from the verifier |
domain | string | No | Domain for the presentation |
Request Example
Section titled “Request Example”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" }'interface CreatePresentationRequest { holder_did: string; holder_private_key: string; credentials: string[]; audience: string; nonce?: string; domain?: string;}
interface CreatePresentationResponse {status: string;message: string;data: {presentation_id: string;presentation: string;holder: string;audience: string;credentials_count: number;created_at: string;};}
async function createPresentation(apiKey: string,request: CreatePresentationRequest): Promise<CreatePresentationResponse> {const response = await fetch('https://api.veriglob.com/v1/presentations', {method: 'POST',headers: {'Authorization': `Bearer ${apiKey}`,'Content-Type': 'application/json'},body: JSON.stringify(request)});
if (!response.ok) {throw new Error(`HTTP error! status: ${response.status}`);}
return response.json();}
// Usageconst result = await createPresentation('your-api-key', {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'});package main
import ( "bytes" "encoding/json" "fmt" "net/http")
type CreatePresentationRequest struct { HolderDID string `json:"holder_did"` HolderPrivateKey string `json:"holder_private_key"` Credentials []string `json:"credentials"` Audience string `json:"audience"` Nonce string `json:"nonce,omitempty"` Domain string `json:"domain,omitempty"`}
type CreatePresentationResponse struct { Status string `json:"status"` Message string `json:"message"` Data struct { PresentationID string `json:"presentation_id"` Presentation string `json:"presentation"` Holder string `json:"holder"` Audience string `json:"audience"` CredentialsCount int `json:"credentials_count"` CreatedAt string `json:"created_at"` } `json:"data"`}
func CreatePresentation(apiKey string, req CreatePresentationRequest) (*CreatePresentationResponse, error) { jsonData, err := json.Marshal(req) if err != nil { return nil, err }
request, err := http.NewRequest("POST", "https://api.veriglob.com/v1/presentations", bytes.NewBuffer(jsonData)) if err != nil { return nil, err }
request.Header.Set("Authorization", "Bearer "+apiKey) request.Header.Set("Content-Type", "application/json")
client := &http.Client{} resp, err := client.Do(request) if err != nil { return nil, err } defer resp.Body.Close()
var result CreatePresentationResponse if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return nil, err }
return &result, nil}
// Usagefunc main() { result, err := CreatePresentation("your-api-key", CreatePresentationRequest{ HolderDID: "did:key:z6MkholderDID...", HolderPrivateKey: "base64-encoded-private-key", Credentials: []string{"v4.public.eyJpc3MiOi...", "v4.public.eyJhbm90aGVy..."}, Audience: "did:key:z6MkverifierDID...", Nonce: "unique-challenge-nonce-12345", }) if err != nil { panic(err) } fmt.Printf("Presentation ID: %s\n", result.Data.PresentationID)}import requestsfrom dataclasses import dataclassfrom typing import Optional, List
@dataclassclass CreatePresentationRequest:holder_did: strholder_private_key: strcredentials: List[str]audience: strnonce: Optional[str] = Nonedomain: Optional[str] = None
def create_presentation(api_key: str, request: CreatePresentationRequest) -> dict:"""Create a verifiable presentation."""payload = {'holder_did': request.holder_did,'holder_private_key': request.holder_private_key,'credentials': request.credentials,'audience': request.audience,}
if request.nonce: payload['nonce'] = request.nonce if request.domain: payload['domain'] = request.domain
response = requests.post( 'https://api.veriglob.com/v1/presentations', headers={ 'Authorization': f'Bearer {api_key}', 'Content-Type': 'application/json' }, json=payload ) response.raise_for_status() return response.json()
# Usage
result = create_presentation('your-api-key', CreatePresentationRequest(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'))print(f"Presentation ID: {result['data']['presentation_id']}")Response
Section titled “Response”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" }}Error Responses
Section titled “Error Responses”| Status | Description |
|---|---|
| 400 | Invalid request body, missing required fields, or invalid credentials |
| 401 | Invalid or missing API key |
| 429 | Rate limit exceeded |
Verify a Presentation
Section titled “Verify a Presentation”Verifies the presentation signature, audience, nonce, and optionally the contained credentials.
POST /v1/presentations/verifyAuthentication
Section titled “Authentication”Requires BearerAuth or ApiKeyAuth.
Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
presentation | string | Yes | The PASETO v4 presentation token |
audience | string | Yes | Expected audience DID (verifier’s DID) |
nonce | string | No | Expected nonce value (if used during creation) |
verify_credentials | boolean | No | Whether to verify contained credentials (default: true) |
check_revocation | boolean | No | Whether to check credential revocation status (default: false) |
Request Example
Section titled “Request Example”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 }'interface VerifyPresentationRequest { presentation: string; audience: string; nonce?: string; verify_credentials?: boolean; check_revocation?: boolean;}
interface CredentialVerification {valid: boolean;issuer: string;subject: string;credential_type: string;claims: Record<string, unknown>;revoked?: boolean;expired?: boolean;}
interface VerifyPresentationResponse {status: string;message: string;data: {valid: boolean;holder?: string;audience?: string;nonce_valid?: boolean;credentials?: CredentialVerification[];created_at?: string;error?: string;};}
async function verifyPresentation(apiKey: string,request: VerifyPresentationRequest): Promise<VerifyPresentationResponse> {const response = await fetch('https://api.veriglob.com/v1/presentations/verify', {method: 'POST',headers: {'Authorization': `Bearer ${apiKey}`,'Content-Type': 'application/json'},body: JSON.stringify(request)});
if (!response.ok) {throw new Error(`HTTP error! status: ${response.status}`);}
return response.json();}
// Usageconst result = await verifyPresentation('your-api-key', {presentation: 'v4.public.eyJwcmVzZW50YXRpb24iOi...',audience: 'did:key:z6MkverifierDID...',nonce: 'unique-challenge-nonce-12345',verify_credentials: true,check_revocation: true});
if (result.data.valid) {console.log('Presentation is valid!');console.log('Holder:', result.data.holder);} else {console.log('Verification failed:', result.data.error);}package main
import ( "bytes" "encoding/json" "fmt" "net/http")
type VerifyPresentationRequest struct { Presentation string `json:"presentation"` Audience string `json:"audience"` Nonce string `json:"nonce,omitempty"` VerifyCredentials *bool `json:"verify_credentials,omitempty"` CheckRevocation *bool `json:"check_revocation,omitempty"`}
type CredentialVerification struct { Valid bool `json:"valid"` Issuer string `json:"issuer"` Subject string `json:"subject"` CredentialType string `json:"credential_type"` Claims map[string]interface{} `json:"claims"` Revoked *bool `json:"revoked,omitempty"` Expired *bool `json:"expired,omitempty"`}
type VerifyPresentationResponse struct { Status string `json:"status"` Message string `json:"message"` Data struct { Valid bool `json:"valid"` Holder string `json:"holder,omitempty"` Audience string `json:"audience,omitempty"` NonceValid *bool `json:"nonce_valid,omitempty"` Credentials []CredentialVerification `json:"credentials,omitempty"` CreatedAt string `json:"created_at,omitempty"` Error string `json:"error,omitempty"` } `json:"data"`}
func VerifyPresentation(apiKey string, req VerifyPresentationRequest) (*VerifyPresentationResponse, error) { jsonData, err := json.Marshal(req) if err != nil { return nil, err }
request, err := http.NewRequest("POST", "https://api.veriglob.com/v1/presentations/verify", bytes.NewBuffer(jsonData)) if err != nil { return nil, err }
request.Header.Set("Authorization", "Bearer "+apiKey) request.Header.Set("Content-Type", "application/json")
client := &http.Client{} resp, err := client.Do(request) if err != nil { return nil, err } defer resp.Body.Close()
var result VerifyPresentationResponse if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return nil, err }
return &result, nil}
// Usagefunc main() { verifyCredentials := true checkRevocation := true
result, err := VerifyPresentation("your-api-key", VerifyPresentationRequest{ Presentation: "v4.public.eyJwcmVzZW50YXRpb24iOi...", Audience: "did:key:z6MkverifierDID...", Nonce: "unique-challenge-nonce-12345", VerifyCredentials: &verifyCredentials, CheckRevocation: &checkRevocation, }) if err != nil { panic(err) }
if result.Data.Valid { fmt.Println("Presentation is valid!") fmt.Printf("Holder: %s\n", result.Data.Holder) } else { fmt.Printf("Verification failed: %s\n", result.Data.Error) }}import requestsfrom dataclasses import dataclassfrom typing import Optional, List, Dict, Any
@dataclassclass VerifyPresentationRequest:presentation: straudience: strnonce: Optional[str] = Noneverify_credentials: bool = Truecheck_revocation: bool = False
def verify_presentation(api_key: str, request: VerifyPresentationRequest) -> dict:"""Verify a verifiable presentation."""payload = {'presentation': request.presentation,'audience': request.audience,'verify_credentials': request.verify_credentials,'check_revocation': request.check_revocation,}
if request.nonce: payload['nonce'] = request.nonce
response = requests.post( 'https://api.veriglob.com/v1/presentations/verify', headers={ 'Authorization': f'Bearer {api_key}', 'Content-Type': 'application/json' }, json=payload ) response.raise_for_status() return response.json()
# Usage
result = verify_presentation('your-api-key', VerifyPresentationRequest(presentation='v4.public.eyJwcmVzZW50YXRpb24iOi...',audience='did:key:z6MkverifierDID...',nonce='unique-challenge-nonce-12345',verify_credentials=True,check_revocation=True))
if result['data']['valid']:print('Presentation is valid!')print(f"Holder: {result['data']['holder']}")else:print(f"Verification failed: {result['data'].get('error')}")Response
Section titled “Response”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..." }}Error Responses
Section titled “Error Responses”| Status | Description |
|---|---|
| 400 | Invalid presentation format or missing fields |
| 401 | Invalid or missing API key |
| 429 | Rate limit exceeded |
| 500 | Internal server error during verification |
Presentation Flow
Section titled “Presentation Flow”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 │ │◄─────────────────────────────────┤ │ │Step 1: Verifier Requests Presentation
Section titled “Step 1: Verifier Requests Presentation”The verifier generates a nonce and sends a presentation request:
{ "verifier_did": "did:key:z6MkverifierDID...", "nonce": "unique-challenge-nonce-12345", "requested_credentials": ["EmploymentCredential", "EducationCredential"]}Step 2: Holder Creates Presentation
Section titled “Step 2: Holder Creates Presentation”The holder selects appropriate credentials and creates a signed presentation.
Step 3: Verifier Verifies Presentation
Section titled “Step 3: Verifier Verifies Presentation”The verifier receives and verifies the presentation.
Security Considerations
Section titled “Security Considerations”Nonce Usage
Section titled “Nonce Usage”Always use a nonce to prevent replay attacks:
- Verifier generates a unique nonce for each presentation request
- Holder includes the nonce in the signed presentation
- Verifier validates the nonce matches the expected value
- Nonce should be single-use and time-limited
Nonce Generation
Section titled “Nonce Generation”import { randomBytes } from 'crypto';
function generateNonce(): string {return randomBytes(32).toString('base64url');}
// Usageconst nonce = generateNonce();// e.g., "dGhpcyBpcyBhIHNlY3VyZSByYW5kb20gbm9uY2U"package main
import ( "crypto/rand" "encoding/base64")
func GenerateNonce() (string, error) { bytes := make([]byte, 32) if _, err := rand.Read(bytes); err != nil { return "", err } return base64.URLEncoding.EncodeToString(bytes), nil}
// Usagefunc main() { nonce, _ := GenerateNonce() // e.g., "dGhpcyBpcyBhIHNlY3VyZSByYW5kb20gbm9uY2U="}import secrets
def generate_nonce() -> str:"""Generate a cryptographically secure nonce."""return secrets.token_urlsafe(32)
# Usage
nonce = generate_nonce()
# e.g., "dGhpcyBpcyBhIHNlY3VyZSByYW5kb20gbm9uY2U"Audience Validation
Section titled “Audience Validation”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
Credential Verification
Section titled “Credential Verification”When verifying presentations, always:
- Verify the presentation signature - Confirms holder’s identity
- Verify each credential signature - Confirms issuer’s identity
- Check revocation status - Ensures credentials are still valid
- Validate expiration dates - Ensures credentials haven’t expired
- Verify credential subjects match holder - Ensures holder owns the credentials
Complete Example: Full Verification Flow
Section titled “Complete Example: Full Verification Flow”import { randomBytes } from 'crypto';
const API_KEY = process.env.VERIGLOB_API_KEY!;const VERIFIER_DID = 'did:key:z6MkverifierDID...';
// Step 1: Generate presentation requestfunction createPresentationRequest() { const nonce = randomBytes(32).toString('base64url'); return { verifier_did: VERIFIER_DID, nonce, requested_credentials: ['EmploymentCredential'] };}
// Step 2: Verify received presentationasync 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 };}package main
import ( "bytes" "crypto/rand" "encoding/base64" "encoding/json" "errors" "net/http" "os")
var (apiKey = os.Getenv("VERIGLOB_API_KEY")verifierDID = "did:key:z6MkverifierDID...")
type PresentationRequest struct {VerifierDID string `json:"verifier_did"`Nonce string `json:"nonce"`RequestedCredentials []string `json:"requested_credentials"`}
// Step 1: Generate presentation requestfunc CreatePresentationRequest() (\*PresentationRequest, error) {nonceBytes := make([]byte, 32)if \_, err := rand.Read(nonceBytes); err != nil {return nil, err}
return &PresentationRequest{ VerifierDID: verifierDID, Nonce: base64.URLEncoding.EncodeToString(nonceBytes), RequestedCredentials: []string{"EmploymentCredential"}, }, nil
}
// Step 2: Verify received presentationfunc VerifyReceivedPresentation(presentation, expectedNonce string) (map[string]string, error) {verifyCredentials := truecheckRevocation := true
reqBody, _ := json.Marshal(map[string]interface{}{ "presentation": presentation, "audience": verifierDID, "nonce": expectedNonce, "verify_credentials": verifyCredentials, "check_revocation": checkRevocation, })
req, _ := http.NewRequest("POST", "https://api.veriglob.com/v1/presentations/verify", bytes.NewBuffer(reqBody)) req.Header.Set("Authorization", "Bearer "+apiKey) req.Header.Set("Content-Type", "application/json")
client := &http.Client{} resp, err := client.Do(req) if err != nil { return nil, err } defer resp.Body.Close()
var result VerifyPresentationResponse json.NewDecoder(resp.Body).Decode(&result)
if !result.Data.Valid { return nil, errors.New("verification failed: " + result.Data.Error) }
// Find employment credential for _, cred := range result.Data.Credentials { if cred.CredentialType == "EmploymentCredential" { return map[string]string{ "holder": result.Data.Holder, "employer": cred.Claims["employer"].(string), "position": cred.Claims["position"].(string), }, nil } }
return nil, errors.New("required EmploymentCredential not found")
}import osimport secretsimport requestsfrom typing import Dict, Any
API_KEY = os.environ['VERIGLOB_API_KEY']VERIFIER_DID = 'did:key:z6MkverifierDID...'
# Step 1: Generate presentation requestdef create_presentation_request() -> Dict[str, Any]: nonce = secrets.token_urlsafe(32) return { 'verifier_did': VERIFIER_DID, 'nonce': nonce, 'requested_credentials': ['EmploymentCredential'] }
# Step 2: Verify received presentationdef verify_received_presentation(presentation: str, expected_nonce: str) -> Dict[str, str]: response = requests.post( 'https://api.veriglob.com/v1/presentations/verify', headers={ 'Authorization': f'Bearer {API_KEY}', 'Content-Type': 'application/json' }, json={ 'presentation': presentation, 'audience': VERIFIER_DID, 'nonce': expected_nonce, 'verify_credentials': True, 'check_revocation': True } ) response.raise_for_status() result = response.json()
if not result['data']['valid']: raise ValueError(f"Verification failed: {result['data'].get('error')}")
# Find employment credential credentials = result['data'].get('credentials', []) employment_cred = next( (c for c in credentials if c['credential_type'] == 'EmploymentCredential'), None )
if not employment_cred: raise ValueError('Required EmploymentCredential not found')
return { 'holder': result['data']['holder'], 'employer': employment_cred['claims']['employer'], 'position': employment_cred['claims']['position'] }
# Usageif __name__ == '__main__': # Verifier creates request request = create_presentation_request() print(f"Send this to holder: {request}")
# Later, when presentation is received... # verified = verify_received_presentation(received_presentation, request['nonce'])