Create a Request
This step prepares a presentation request in your backend. Keep the request session, signed request envelope, and Presentation Definition on the server.
The backend flow is:
- Prepare the presentation request and submission URL for Credential Wallet.
- Create a Presentation Request Envelope with
createRequestFromConfig. - Store it in your backend.
Backend routes
Your application owns the session and its lifetime. A typical implementation has these routes:
| Endpoint | Purpose |
|---|---|
POST /api/personhood/sessions | Create a session and return the PD fetch URL to the frontend. |
GET /api/personhood/sessions/{sessionId}/pd | Return the wallet-compatible presentation request envelope. |
POST /api/personhood/sessions/{sessionId}/submit | Receive and verify the wallet presentation submission. |
The examples below use a ForumAccountRequestVerifiableCredential request. Replace the route names and request type with the values registered for your app.
Prepare the Presentation Request and Submission URL for Credential Wallet
When the user starts verification, create an app-owned session. Generate a unique request ID and nonce, and choose an expiration time within the SDK limit.
Both URLs must be HTTPS URLs on domains registered in the app config:
pdFetchUrlis the URL that Credential Wallet calls to fetch the presentation request.submissionUrlis the URL that Credential Wallet calls to submit the signed presentation.
const sessionId = crypto.randomUUID();
const subject = userAccountId;
const nonce = crypto.randomUUID();
const expiresAt = new Date(Date.now() + 10 * 60 * 1000);
const credentialMinimumLifetime = new Date(Date.now() + 24 * 60 * 60 * 1000);
const apiBaseUrl = 'https://developer-forum.example';
const pdFetchUrl = `${apiBaseUrl}/api/personhood/sessions/${sessionId}/pd`;
const submissionUrl = `${apiBaseUrl}/api/personhood/sessions/${sessionId}/submit`;
subject is a stable identifier owned by your application. It identifies the account, resource, or authorization scope being verified. For a Developer Forum account, use the user's account ID.
Create a presentation request
Use the SDK service initialized in Add the SDK and load app config.
For a config-driven request, the registered app config supplies the requested attributes and personal-data policy. Do not pass attributes or construct a second copy of those policy values in application code.
import { TargetCredentialType } from '@iamhuman.link/presentation-exchange-sdk';
import { presentation } from './presentation-service.js';
const requestType = 'ForumAccountRequestVerifiableCredential';
const { envelope, presentationDefinition, verificationContext } = await presentation.createRequestFromConfig({
requestType,
subject,
pdRequestId: sessionId,
nonce,
expiresAt,
pdFetchUrl,
submissionUrl,
definition: {
id: sessionId,
expirationMinimum: credentialMinimumLifetime,
},
});
Store it in your backend
Store the returned envelope in your backend. It has this shape:
{
jwtVc: string;
expiresAt: string;
pdRequestId: string;
pdRequestType: string;
pdHash: string;
appId: string;
nonce: string;
}
Persist the envelope, presentationDefinition, and verificationContext in your backend session storage.
The stored presentationDefinition and envelope are the values that bind the later wallet submission to this session. The verificationContext is the SDK's config-derived verification input; pass its policy and targetCredentialType to verifySubmission in document 08 instead of duplicating policy values in application code.
Return only what the frontend needs to display the request:
res.status(201).json({
session: {
sessionId,
pdFetchUrl,
expiresAt: envelope.expiresAt,
},
});
Serve the request envelope
Credential Wallet fetches the request through pdFetchUrl. Look up the session, then return the stored envelope under pdVc.
app.get('/api/personhood/sessions/:sessionId/pd', async (req, res) => {
const session = await sessions.get(req.params.sessionId);
// Reject unknown, expired, or already submitted sessions.
return res.json({ ok: true, pdVc: session.requestEnvelope });
});
The frontend and wallet use pdFetchUrl; they do not need the signed jwtVc directly. Continue with Display the request and scan it from Credential Wallet.