Embedded Credential Renderer
Render verifiable credentials in your application using our iframe-based renderer with postMessage API.
Overview
The Aho Embedded Renderer allows you to display verifiable credentials in your application without building your own rendering logic. It implements the W3C VC Render Method specification for displaying credentials with their associated templates.
Quick Start
Get started in three steps: embed the iframe, listen for the ready event, and send your credential.
Embed the iframe
<iframe
id="credential-renderer"
src="https://aho.com/render/frame"
style="width: 100%; border: none; min-height: 400px;"
></iframe>
Listen for the ready event
const iframe = document.getElementById('credential-renderer');
window.addEventListener('message', (event) => {
const { type, payload } = event.data;
if (type === 'RENDERER_READY') {
// Renderer is ready to receive credentials
sendCredential();
}
});
Send your credential
function sendCredential() {
iframe.contentWindow.postMessage({
type: 'RENDER_DOCUMENT',
payload: {
document: {
"@context": ["https://www.w3.org/2018/credentials/v1"],
"type": ["VerifiableCredential", "EmploymentCredential"],
"issuer": "did:web:example.com",
"credentialSubject": {
"name": "Jane Doe",
"position": "Software Engineer",
"employer": "Acme Corp"
},
"renderMethod": {
"type": "https://aho.com/rendering/SvgMustacheTemplate2024",
"renderName": "EMPLOYMENT_VERIFICATION"
}
}
}
}, '*');
}
postMessage API Reference
Communication between your application (host) and the renderer (iframe) uses the
postMessage API.
All messages have a type
and optional payload.
Host → Renderer
Messages you send to the iframe.
RENDER_DOCUMENT
Required
Sends a credential to the renderer for display.
{
type: 'RENDER_DOCUMENT',
payload: {
document: { /* W3C Verifiable Credential */ }
}
}
SELECT_TEMPLATE
Optional
Switches to a different template for the current credential.
{
type: 'SELECT_TEMPLATE',
payload: 'UNIVERSITY_DIPLOMA' // Template ID
}
GET_TEMPLATES
Optional
Requests a list of available templates. Renderer responds with UPDATE_TEMPLATES.
{ type: 'GET_TEMPLATES' }
PRINT
Optional
Triggers the browser print dialog for the credential.
{ type: 'PRINT' }
Renderer → Host
Messages the iframe sends to your application.
RENDERER_READY
Sent when the renderer has loaded and is ready to receive credentials.
{ type: 'RENDERER_READY' }
UPDATE_HEIGHT
Sent when content height changes. Use to resize the iframe dynamically.
{ type: 'UPDATE_HEIGHT', payload: 520 } // Height in pixels
UPDATE_TEMPLATES
Response to GET_TEMPLATES with available template options.
{
type: 'UPDATE_TEMPLATES',
payload: [
{ id: 'EMPLOYMENT_VERIFICATION', label: 'Employment Verification' },
{ id: 'UNIVERSITY_DIPLOMA', label: 'University Diploma' }
]
}
OBFUSCATE
Selective Disclosure
Sent when a user clicks a field to hide it. Use this to update your credential's disclosed claims.
{ type: 'OBFUSCATE', payload: 'credentialSubject.ssn' }
RENDER_ERROR
Sent when rendering fails.
{ type: 'RENDER_ERROR', payload: { message: 'Template not found' } }
Code Examples
Vanilla JavaScript
class CredentialRenderer {
constructor(iframeId, credential) {
this.iframe = document.getElementById(iframeId);
this.credential = credential;
this.ready = false;
window.addEventListener('message', this.handleMessage.bind(this));
}
handleMessage(event) {
const { type, payload } = event.data || {};
switch (type) {
case 'RENDERER_READY':
this.ready = true;
this.render();
break;
case 'UPDATE_HEIGHT':
this.iframe.style.height = `${payload}px`;
break;
case 'OBFUSCATE':
console.log('User wants to hide:', payload);
// Update your credential's disclosed claims
this.onObfuscate?.(payload);
break;
case 'RENDER_ERROR':
console.error('Render error:', payload.message);
break;
}
}
render() {
if (!this.ready) return;
this.iframe.contentWindow.postMessage({
type: 'RENDER_DOCUMENT',
payload: { document: this.credential }
}, '*');
}
print() {
this.iframe.contentWindow.postMessage({ type: 'PRINT' }, '*');
}
}
// Usage
const renderer = new CredentialRenderer('my-iframe', myCredential);
renderer.onObfuscate = (field) => {
// Handle selective disclosure
};
React Component
import { useEffect, useRef, useCallback } from 'react';
function CredentialRenderer({ credential, onObfuscate }) {
const iframeRef = useRef(null);
const readyRef = useRef(false);
const sendCredential = useCallback(() => {
if (!readyRef.current || !iframeRef.current) return;
iframeRef.current.contentWindow.postMessage({
type: 'RENDER_DOCUMENT',
payload: { document: credential }
}, '*');
}, [credential]);
useEffect(() => {
const handleMessage = (event) => {
const { type, payload } = event.data || {};
switch (type) {
case 'RENDERER_READY':
readyRef.current = true;
sendCredential();
break;
case 'UPDATE_HEIGHT':
if (iframeRef.current) {
iframeRef.current.style.height = `${payload}px`;
}
break;
case 'OBFUSCATE':
onObfuscate?.(payload);
break;
}
};
window.addEventListener('message', handleMessage);
return () => window.removeEventListener('message', handleMessage);
}, [sendCredential, onObfuscate]);
// Re-render when credential changes
useEffect(() => {
sendCredential();
}, [credential, sendCredential]);
return (
<iframe
ref={iframeRef}
src="https://aho.com/render/frame"
style={{ width: '100%', border: 'none', minHeight: '400px' }}
title="Credential Renderer"
/>
);
}
// Usage
function App() {
const [credential, setCredential] = useState(myCredential);
const handleObfuscate = (field) => {
console.log('Hide field:', field);
// Update credential with redacted field
};
return <CredentialRenderer credential={credential} onObfuscate={handleObfuscate} />;
}
Vue 3 Component
<template>
<iframe
ref="iframeRef"
src="https://aho.com/render/frame"
:style="{ width: '100%', border: 'none', minHeight: height + 'px' }"
title="Credential Renderer"
/>
</template>
<script setup>
import { ref, watch, onMounted, onUnmounted } from 'vue';
const props = defineProps({
credential: { type: Object, required: true }
});
const emit = defineEmits(['obfuscate']);
const iframeRef = ref(null);
const height = ref(400);
const ready = ref(false);
function sendCredential() {
if (!ready.value || !iframeRef.value) return;
iframeRef.value.contentWindow.postMessage({
type: 'RENDER_DOCUMENT',
payload: { document: props.credential }
}, '*');
}
function handleMessage(event) {
const { type, payload } = event.data || {};
switch (type) {
case 'RENDERER_READY':
ready.value = true;
sendCredential();
break;
case 'UPDATE_HEIGHT':
height.value = payload;
break;
case 'OBFUSCATE':
emit('obfuscate', payload);
break;
}
}
watch(() => props.credential, sendCredential, { deep: true });
onMounted(() => window.addEventListener('message', handleMessage));
onUnmounted(() => window.removeEventListener('message', handleMessage));
</script>
Server Endpoints
These endpoints are used internally by the iframe renderer. You can also call them directly for server-side rendering.
/render/templates
Renders a credential and returns the SVG template.
Request Body
{
"credential": { /* W3C Verifiable Credential */ },
"render_name": "EMPLOYMENT_VERIFICATION" // Optional
}
Response
{
"svg": "<svg ...>...</svg>",
"template_key": "EMPLOYMENT_VERIFICATION"
}
/render/obfuscate
Obfuscates (redacts) specified fields from a credential for selective disclosure.
Request Body
{
"credential": { /* W3C Verifiable Credential */ },
"fields": ["credentialSubject.ssn", "credentialSubject.address"],
"render_name": "EMPLOYMENT_VERIFICATION" // Optional
}
Response
{
"credential": { /* Modified credential with obfuscated fields */ },
"svg": "<svg ...>...</svg>",
"template_key": "EMPLOYMENT_VERIFICATION",
"redacted_fields": ["credentialSubject.ssn", "credentialSubject.address"],
"errors": null
}
Render Method Specification
Include a renderMethod
in your credential to specify how it should be displayed. This follows the
W3C VC Render Method specification.
Aho Render Method
{
"@context": ["https://www.w3.org/2018/credentials/v1"],
"type": ["VerifiableCredential", "EmploymentCredential"],
"issuer": "did:web:example.com",
"credentialSubject": {
"name": "Jane Doe",
"position": "Software Engineer"
},
"renderMethod": {
"type": "https://aho.com/rendering/SvgMustacheTemplate2024",
"renderName": "EMPLOYMENT_VERIFICATION",
"css3MediaQuery": "@media print"
}
}
type: Must be https://aho.com/rendering/SvgMustacheTemplate2024
renderName: Template identifier (e.g., EMPLOYMENT_VERIFICATION, UNIVERSITY_DIPLOMA)
css3MediaQuery: Optional media query hint for display context