Stripe widgets
Add read-only Stripe customer, subscription and invoice widgets through the TypeScript, Go and Laravel SDKs.
Provider widgets appear beneath the overview on a record page, including custom summaries. Configure them in your SDK. Your browser calls your agent, which resolves the linked record and calls Stripe directly. Provider credentials and responses never pass through Aviato's API, servers, or databases. The ordinary audit trail records the local record read and its outcome, without provider IDs, credentials, or response bodies.
These APIs are included in the upcoming 0.2.0 SDKs and require an agent build with
provider-widget support. This documentation tracks the current source; committing these
changes does not publish the SDK packages or agent binary. The earlier 0.1.x releases do
not provide StripePlugin.
Configure a read-only Stripe key
Create a Stripe restricted API key with Read access to Customers, Subscriptions, and Invoices as needed, with all other permissions set to None. Do not grant any Write permissions. Use a test-mode key with test-mode IDs, or a live-mode key with live-mode IDs. You can further restrict the key to your agent's outbound IP addresses.
Set STRIPE_WIDGET_KEY in the customer agent's environment, through your deployment's
secret manager. Never put its value into widget configuration, source control, the dashboard,
or an Aviato environment setting. Restart the agent after changing its environment.
The adapter accepts only rk_test_… or rk_live_… keys and makes only GET requests.
readOnly: true is your confirmation of the permissions configured in Stripe. It is not
an automatic verification of key permissions: the public Stripe API does not provide a
supported permission-introspection endpoint for a supplied key. A restricted key can still
have write permissions if configured incorrectly. Aviato does not attempt write requests to
test the key. Keep this distinction in mind when enforcing your own credential policy.
TypeScript
StripePlugin implements the shared IntegrationPlugin interface. Configure credentials
once, add typed widgets, then install the integration with plugin.use():
import { StripePlugin } from "@getaviato/sdk";
const stripe = new StripePlugin({
credentialEnv: "STRIPE_WIDGET_KEY",
readOnly: true,
});
const customerId = { field: "stripe_customer_id" };
plugin.use(stripe
.customer({ collection: "customers", name: "stripe-customer", customerId })
.subscriptions({ collection: "customers", name: "stripe-subscriptions", customerId })
.invoices({ collection: "customers", name: "stripe-invoices", customerId }));To show billing on an order, use collection: "orders" and
customerId: { field: "stripe_customer_id", relation: "customer" }. The relation must be a
belongsTo relation in your agent schema (introspected or declared using relation hints).
This first version supports one relation hop. The binding always comes from your database,
never a browser-supplied Stripe ID. Set title for custom copy, or omit it for a localized default.
Single-entity methods distinguish their binding types:
stripe.subscription({
collection: "subscriptions", name: "stripe-subscription",
subscriptionId: { field: "stripe_subscription_id" },
});
stripe.invoice({
collection: "invoices", name: "stripe-invoice",
invoiceId: { field: "stripe_invoice_id" },
});
plugin.use(stripe);Passing customerId to .subscription(), or invoiceId to .invoices(), is a TypeScript
error. Provider and resource strings are handled by the plugin, not repeated in application code.
Go
stripe, err := aviato.NewStripePlugin(aviato.StripePluginOptions{
CredentialEnv: "STRIPE_WIDGET_KEY", ReadOnly: true,
})
if err != nil {
return err
}
stripe.Invoices(aviato.StripeCustomerWidget{
Collection: "customers", Name: "stripe-invoices", Title: "Stripe invoices",
CustomerID: aviato.RecordBinding{Field: "stripe_customer_id"},
})
if err := plugin.Use(stripe); err != nil {
return err
}StripePlugin satisfies IntegrationPlugin. Customer, Subscriptions, and Invoices
accept StripeCustomerWidget. Subscription accepts StripeSubscriptionWidget with
SubscriptionID; Invoice accepts StripeInvoiceWidget with InvoiceID.
Set Relation: "customer" on RecordBinding to resolve a related record.
Laravel
use Aviato\Laravel\Facades\Aviato;
use Aviato\Laravel\Integrations\RecordBinding;
use Aviato\Laravel\Integrations\StripePlugin;
$stripe = new StripePlugin(credentialEnv: 'STRIPE_WIDGET_KEY', readOnly: true);
$stripe->invoices(
collection: 'customers',
name: 'stripe-invoices',
customerId: new RecordBinding(field: 'stripe_customer_id'),
title: 'Stripe invoices',
);
Aviato::use($stripe);StripePlugin implements IntegrationPlugin. The single-entity methods are
subscription(..., subscriptionId: new RecordBinding(...)) and
invoice(..., invoiceId: new RecordBinding(...)). Use relation: 'customer' on the binding
for a related record. The key belongs in the agent's process environment even when your
plugin runs inside Laravel.
All SDKs snapshot an integration when it is installed. Later builder changes take effect only
when you call use / Use again. Registration is atomic: an invalid declaration installs no
widgets from that call. Re-registering a collection/name pair replaces that widget; it does
not remove other widgets already registered on the host.
Available widgets
| Provider | Resource | Binding value | Display |
|---|---|---|---|
stripe | customer | cus_… | Name, email, Stripe ID, creation date |
stripe | subscriptions | cus_… | Latest 20 subscriptions, including canceled subscriptions |
stripe | invoices | cus_… | Latest 20 invoices, status, amount due, creation date |
stripe | subscription | sub_… | One subscription, status, creation and scheduled cancellation dates |
stripe | invoice | in_… | One invoice |
Use the corresponding typed method to register each widget. Names are unique within a collection; registering the same name again replaces it. The agent exposes at most 20 valid widgets per collection. Each widget can reference a different credential environment variable, for example for separate Stripe accounts.
The list widgets are bounded previews, not paginated browsers. A notice appears when more records exist. Refresh fetches current data. There are no refund, payment, cancellation, subscription-editing, hosted-invoice, or download actions.
Permissions and failures
Reading a widget requires access to the local record and its binding column. Related bindings also require access to the relation's foreign-key columns, the target record, and the target binding column. A null or empty binding displays an unlinked state. An inaccessible related record reveals no provider data. Provider responses are projected onto a small display-field allowlist; metadata, payment methods, addresses, arbitrary HTML and URLs are excluded.
Read-only refers to provider writes. Access to the provider data follows access to the binding column: users who can read it can read that widget. Restrict the binding column when billing details should only be visible to selected roles.
The agent pins the Stripe API version to 2025-09-30.clover, limits each request to ten seconds
and one MiB, rejects redirects, and never forwards raw provider errors. For an unavailable
widget, check the agent environment, key permissions, mode, and the configured ID column.
No provider data is persisted by the widget system. The browser retains it only while the
widget is mounted; HTTP responses use Cache-Control: no-store.
Future providers
Each SDK exposes a common IntegrationPlugin interface with a widgets() / Widgets()
method. Provider classes implement that contract and expose their own typed builders.
TypeScript implementations return WidgetInput declarations, Go implementations return
[]WidgetDefinition, and Laravel implementations return IntegrationWidget objects.
Applications use StripePlugin; these lower-level declarations are for integration authors.
Serialized definitions separate provider, resource, record binding, and credential reference. The customer-agent provider registry owns each provider's supported resources, fixed outbound endpoints, credential policy, and response projection. All providers reuse record authorization and a versioned, bounded table format with text, status, date, and money cells. Adding a provider does not require new SDK methods or an Aviato-side proxy. Intercom and Zendesk are not included in this release; each needs its own provider implementation and credential-policy review.