Skip to main content
Developer Docs

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.

Iframe-based
Secure isolation with postMessage communication
Selective Disclosure
Click fields to hide them from verifiers
Print-ready
Trigger printing with a simple message

Quick Start

Get started in three steps: embed the iframe, listen for the ready event, and send your credential.

1

Embed the iframe

<iframe
  id="credential-renderer"
  src="https://aho.com/render/frame"
  style="width: 100%; border: none; min-height: 400px;"
></iframe>
2

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();
  }
});
3

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

JavaScript

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

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

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.

POST /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"
}
POST /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