PostGuard requires the sender to prove their identity before encrypting. The SDK supports three authentication methods, each suited to a different environment.
| Method | Environment | Interactive |
|---|---|---|
pg.sign.apiKey() |
Server-side, trusted clients | No |
pg.sign.yivi() |
Browser apps | Yes (QR code) |
pg.sign.session() |
Extensions, custom flows | Depends on callback |
Uses a pre-shared API key (prefixed with PG-) to authenticate with the PKG. Keys are issued by the postguard-business portal. Suitable for server-side applications or trusted client environments where you don't need interactive identity verification.
The SvelteKit example uses an API key for encryption:
const sealed = pg.encrypt({
files,
recipients: [
pg.recipient.email(citizen.email),
pg.recipient.emailDomain(organisation.email)
],
sign: pg.sign.apiKey(apiKey),
onProgress,
signal: abortController?.signal
});
const result = await sealed.upload({
notify: {
recipients: true,
message: message ?? undefined,
language: 'EN'
}
});The SDK sends the API key as a Bearer token in the Authorization header when requesting signing keys from the PKG at POST /v2/irma/sign/key.
::: info API keys are part of PostGuard for Business. Contact your PKG administrator to obtain one. :::
Runs an interactive Yivi session directly in the browser. The SDK renders a QR code (or app link on mobile) in the specified DOM element. The user scans it with the Yivi app to prove their email address.
For decryption in a browser, pass element to opened.decrypt() with a CSS selector for the Yivi QR container. See Decryption for the full call shape.
The PostGuard website uses Yivi signing with optional extra attributes and sender encryption:
const sign = pg.sign.yivi({
element: '#crypt-irma-qr',
attributes: [
{ t: 'pbdf.gemeente.personalData.fullname', optional: true },
{ t: 'pbdf.sidn-pbdf.mobilenumber.mobilenumber', optional: true },
{ t: 'pbdf.gemeente.personalData.dateofbirth', optional: true },
],
includeSender: true,
});The sender's email is always requested automatically. Attributes marked optional: true are presented to the user as optional disclosures: the user can choose to skip them during the Yivi session. Non-optional attributes must be disclosed for the session to succeed.
When includeSender is true, the sender's identity is added to the encryption policy so the sender can also decrypt their own message.
| Parameter | Type | Required | Description |
|---|---|---|---|
element |
string |
Yes | CSS selector for the QR code container |
senderEmail |
string |
No | The sender's email address to prove |
attributes |
Array<{ t, v?, optional? }> |
No | Extra attributes to request (e.g. name, phone). Email is always included automatically. |
includeSender |
boolean |
No | Also encrypt for the sender so they can decrypt their own message (default: false) |
The most flexible method. You provide a callback function that receives a session request and must return a JWT string. This lets you handle the Yivi session yourself: in a popup window, a separate process, or any custom flow.
The Thunderbird addon uses this for both encryption and decryption. For encryption, the session callback opens a Yivi popup:
const sealed = pg!.encrypt({
sign: pg!.sign.session(
async ({ con, sort }) => createYiviPopup(con as AttributeCon, sort as KeySort),
{ senderEmail: from }
),
recipients: pgRecipients,
data: mimeData,
});Source: background.ts#L372-L379
For decryption, the same pattern with a session callback:
const opened = pg.open({ data: ciphertext });
const result = await opened.decrypt({
recipient: myAddresses[0],
session: async ({ con, sort, hints, senderId }) => {
return createYiviPopup(
con as AttributeCon,
sort as KeySort,
hints as AttributeCon | undefined,
senderId
);
},
}) as DecryptDataResult;Source: background.ts#L710-L721
| Property | Type | Description |
|---|---|---|
con |
Array<{ t, v? }> |
Attribute constraints the user must prove |
sort |
'Signing' | 'Decryption' |
Whether this is for signing or decryption |
hints |
Array<{ t, v? }> |
Optional hints (e.g. expected recipient email) |
senderId |
string |
Optional sender identifier |
The callback must return a JWT string from a completed Yivi session.
The Outlook addon uses Office.context.ui.displayDialogAsync() instead of browser.windows.create():
async function openYiviDialogForSigning(con: AttributeCon): Promise<string> {
const dialogData = {
hostname: PKG_URL,
header: PG_CLIENT_HEADER,
con,
sort: "Signing",
validity: secondsTill4AM(),
};
const encodedData = encodeURIComponent(JSON.stringify(dialogData));
const dialogUrl = `${window.location.origin}/dialog.html?data=${encodedData}`;
return new Promise<string>((resolve, reject) => {
Office.context.ui.displayDialogAsync(
dialogUrl,
{ height: 60, width: 40, promptBeforeOpen: false },
(asyncResult) => {
if (asyncResult.status !== Office.AsyncResultStatus.Succeeded) {
reject(new Error("Failed to open signing dialog"));
return;
}
const dialog = asyncResult.value;
dialog.addEventHandler(Office.EventType.DialogMessageReceived, (arg: { message: string }) => {
dialog.close();
try {
const message = JSON.parse(arg.message);
if (message.jwt) resolve(message.jwt);
else reject(new Error(message.error || "No JWT"));
} catch {
reject(new Error("Invalid dialog response"));
}
});
dialog.addEventHandler(Office.EventType.DialogEventReceived, () => {
reject(new Error("Dialog was closed"));
});
}
);
});
}See the Thunderbird addon and Outlook addon pages for the full patterns.
Decryption also requires identity verification. The same element and session patterns apply. You must provide either element or session when calling opened.decrypt(). If neither is provided, the SDK throws a DecryptionError.
Pass enableCache: true to cache the Yivi JWT across multiple decrypt() calls. This avoids forcing the user to scan a QR code for every message when decrypting a batch. See Decryption: JWT caching for details.