Setup Guide
Neut Widget Customer Quickstart
The shortest production-safe path for adding Neut widgets to a customer website. The browser asks your own backend for a fresh iframe URL; your backend signs the short-lived bootstrap assertion with Neut credentials.
Integration Flow
- Add one or more empty Neut iframe placeholders to the page.
- Put widget variables on each placeholder container, usually from your CMS.
- Browser JavaScript sends those variables to your backend.
- Your backend validates the variables and returns a fresh
embed_url. - Browser JavaScript sets the iframe
src. - The hosted iframe exchanges the bootstrap assertion for a short-lived widget token.
- The iframe sends resize messages to the host page.
Server Configuration
HOST_ORIGIN=https://www.customer-site.com
WIDGET_TENANT_ID=your-neut-tenant-id
WIDGET_ORIGIN=https://widgets.neut.us
NEUT_MINT_KEY_ID=your-mint-key-id
NEUT_MINT_SECRET=your-mint-secret
HOST_ORIGIN must be the exact customer website origin with no path, query, or trailing slash.
NEUT_MINT_SECRET must stay on your server.
Step 1: Add Widget Markup
Each widget placement needs a stable instance_id, widget-specific data attributes, an empty iframe, and a visible fallback status element.
Bill Preview
<section
data-neut-widget="bill-preview"
data-instance-id="bill-preview-article-123-main"
data-article-id="article-123"
data-bill-type="HR"
data-bill-number="1"
data-congress="119"
data-colorway="Default"
>
<iframe
data-neut-frame
title="Neut Bill Preview"
style="width: 100%; max-width: 1000px; min-height: 320px; border: 0; display: block; margin: 16px 0;"
loading="lazy"
></iframe>
<p class="neut-widget-status">Loading bill preview...</p>
</section>
Vote Breakdown
<section
data-neut-widget="vote-breakdown"
data-instance-id="vote-breakdown-lofgren-ca-18"
data-member-type="Representative"
data-last-name="Lofgren"
data-state-code="CA"
data-district="18"
data-congress="119"
data-limit="5"
data-colorway="Paper"
>
<iframe data-neut-frame title="Neut Vote Breakdown" loading="lazy"></iframe>
<p class="neut-widget-status">Loading vote breakdown...</p>
</section>
Legislation By Subject
<section
data-neut-widget="legislation-by-subject"
data-instance-id="legislation-lofgren-health"
data-member-type="Representative"
data-last-name="Lofgren"
data-state-code="CA"
data-district="18"
data-congress="119"
data-subject="Health & Social Welfare"
data-status-filter="All"
data-limit="10"
data-colorway="WashedSage"
>
<iframe data-neut-frame title="Neut Legislation By Subject" loading="lazy"></iframe>
<p class="neut-widget-status">Loading legislation...</p>
</section>
Step 2: Mount Widgets In The Browser
Your browser script should collect the widget data attributes, post them to your backend, set the returned iframe URL, and accept resize messages only from the expected widget origin.
async function mountNeutWidget(container) {
const frame = container.querySelector("iframe[data-neut-frame]");
const payload = buildPayload(container);
const response = await fetch("/api/widget-embed-params", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
const data = await response.json();
if (!response.ok || data.widget_id !== payload.widget_id) {
throw new Error(data.error?.message || "Widget setup failed.");
}
const expectedOrigin = new URL(data.embed_url).origin;
window.addEventListener("message", (event) => {
if (event.origin !== expectedOrigin) return;
if (event.data?.type !== "neut-widget-resize") return;
if (event.data.instance_id !== payload.instance_id) return;
frame.style.height = `${Math.ceil(Number(event.data.height))}px`;
});
frame.src = data.embed_url;
}
document.querySelectorAll("[data-neut-widget]").forEach(mountNeutWidget);
Step 3: Add Your Backend Endpoint
Create this endpoint on your website backend:
POST /api/widget-embed-params
The endpoint accepts the browser payload, validates fields, reads tenant and credential values from server configuration, signs a bootstrap assertion, builds a hosted Neut iframe URL, and returns it to the browser.
Launch Checklist
NEUT_MINT_SECRETis server-only.WIDGET_TENANT_IDcomes from server configuration, not browser input.HOST_ORIGINis explicitly configured in production.- Browser-provided widget variables are validated before signing.
- Full
embed_urlvalues and query strings are not logged. - Iframe resize messages are accepted only from the expected widget origin.