@authbound/server to create credential definitions and OpenID4VCI wallet offers from your backend.
Install
pnpm add @authbound/server
Environment
AUTHBOUND_SECRET_KEY=sk_test_...
AUTHBOUND_API_URL=https://api.authbound.io
Server
This example creates an employee badge definition if needed, then returns a wallet offer.server.mjs
import { createServer } from "node:http";
import { AuthboundClient, AuthboundClientError } from "@authbound/server";
if (!process.env.AUTHBOUND_SECRET_KEY) {
throw new Error("AUTHBOUND_SECRET_KEY is required");
}
const authbound = new AuthboundClient({
apiKey: process.env.AUTHBOUND_SECRET_KEY,
apiUrl: process.env.AUTHBOUND_API_URL,
});
const credentialDefinitionId = "employee_badge_v1";
const employee = {
id: "employee_123",
givenName: "Sergio",
familyName: "Jack",
employeeNumber: "E-1001",
department: "Customer Success",
};
async function ensureCredentialDefinition() {
try {
return await authbound.issuer.credentialDefinitions.get(
credentialDefinitionId
);
} catch (error) {
if (
!(error instanceof AuthboundClientError) ||
error.code !== "credential_definition_not_found"
) {
throw error;
}
}
return authbound.issuer.credentialDefinitions.create({
credentialDefinitionId,
vct: "urn:vc:authbound:employee-badge:1.0",
format: "dc+sd-jwt",
title: "Employee Badge",
aliases: ["employee_badge"],
claims: [
{ path: ["Employee", "given_name"], mandatory: true },
{ path: ["Employee", "family_name"], mandatory: true },
{ path: ["Employee", "employee_number"], mandatory: true },
{ path: ["Employee", "department"], mandatory: true },
],
});
}
function employeeClaims(record) {
return {
Employee: {
given_name: record.givenName,
family_name: record.familyName,
employee_number: record.employeeNumber,
department: record.department,
},
};
}
async function createWalletOffer() {
const definition = await ensureCredentialDefinition();
return authbound.openId4Vc.issuance.createOffer({
credentialDefinitionId: definition.credentialDefinitionId,
claims: employeeClaims(employee),
issuanceMode: "InTime",
metadata: {
employeeId: employee.id,
},
idempotencyKey: `employee-badge:${employee.id}`,
});
}
const server = createServer(async (request, response) => {
try {
const url = new URL(request.url ?? "/", "http://localhost");
if (request.method === "POST" && url.pathname === "/offer") {
const offer = await createWalletOffer();
response.writeHead(200, { "Content-Type": "application/json" });
response.end(JSON.stringify(offer, null, 2));
return;
}
response.writeHead(200, { "Content-Type": "text/plain" });
response.end("POST /offer to create a wallet offer");
} catch (error) {
const body =
error instanceof AuthboundClientError
? { code: error.code, message: error.message, details: error.details }
: { code: "issuer_error", message: "Failed to create wallet offer" };
response.writeHead(500, { "Content-Type": "application/json" });
response.end(JSON.stringify(body, null, 2));
}
});
server.listen(3000, () => {
console.log("Issuer server running at http://localhost:3000");
});
node server.mjs
curl -X POST http://localhost:3000/offer
Response
{
"object": "openid4vc_issuance",
"id": "iss_123",
"status": "offer_created",
"credentialDefinitionId": "employee_badge_v1",
"offerUri": "openid-credential-offer://...",
"offerQrUri": "openid-credential-offer://..."
}
offerQrUri as a QR code for desktop users and offerUri as a mobile wallet link.