The widget: testimonials on any website
One snippet shows a brand's approved testimonials on any page: Shopify, WordPress, Webflow, Squarespace, or HTML you write yourself. It never calls the Akteora API. It reads a static document from Akteora's CDN, so a busy page costs you nothing, and it keeps working even if Akteora's API is down.
The snippet
Create a widget in the dashboard (or with POST /v1/widgets) and paste its snippet:
<div data-akteora-widget="wgt_01J8Z3Y4X5W6V7T8S9R0Q1P2N3" style="height:320px"></div>
<script src="https://cdn.akteora.com/w.js" async></script>
- The box reserves the widget's height, so nothing on your page moves when it appears.
Keep the
styleas it is. - One script serves every widget on a page. For a second widget, paste only its
<div>(pasting the whole snippet twice also works). - Changing a widget's layout changes its height. Paste the new snippet after changing it.
Shopify
In the theme editor, add a Custom Liquid section (or edit a template) and paste the snippet.
WordPress
Add a Custom HTML block and paste the snippet.
Webflow and Squarespace
Use an Embed element (Webflow) or a Code block (Squarespace).
What visitors get
- Four layouts:
wall(a grid of cards),carousel(a row that scrolls and snaps),single(one featured testimonial) andavatars(faces and a rating, for the top of a page). - Themes:
light,dark, orautoto follow the visitor's system setting, in your brand's colour or an accent of your own. - Video only when asked: posters load; a recording is fetched only when someone presses play. It plays in a dialog, with captions on.
- Accessible: keyboard-operable, a proper modal dialog, alt text on every image, and reduced motion respected.
- Isolated: the widget draws inside a shadow root, so your page's CSS cannot break it and its CSS cannot touch your page.
- Private: no cookies, no storage, no analytics, and no request to anyone but
cdn.akteora.com. Nothing is needed for a cookie banner.
On the free plan, widgets show a small "Collect testimonials with Akteora" link. Paid plans
can switch it off (show_attribution: false); on the free plan that request is refused with
402 plan_limit_exceeded.
What a widget shows
Only testimonials that are approved and public, not rejected or deleted, and processed. Nothing else ever reaches the document, however the widget selects:
selection |
Shows |
|---|---|
automatic |
The brand's newest approvals first; narrowed by tag_ids (any of them) and min_rating |
manual |
Exactly submission_ids, in that order. A pick that is no longer public is skipped |
At most max_items (up to 50). fields chooses what each card shows: avatar, name,
title, company, rating, date. A field switched off is left out of the published
document, not just hidden.
A video appears once its files are published (see Media), a few seconds after approval. When you approve, withdraw, edit or delete, the widget's document is rebuilt within a few seconds, then visitors' browsers may keep their copy for up to a minute.
How the widget changes over time
New layouts, looks and card options will be added. What already works keeps working:
- A pasted snippet never needs updating. The attribute, the script's address and the document's address do not change, and a layout keeps the height its snippet reserved. You paste a new snippet only when you change a widget's layout.
- New options are off until you turn them on, so an existing widget looks the same after an update unless you change it.
- The document only grows. Fields are added, never renamed or removed. If you read it
yourself, ignore fields you do not know, and check
version: it changes only for a change you would have to adapt to, announced here beforehand.
The routes
GET /v1/widgets?brand_id=… |
A brand's widgets, newest first |
POST /v1/widgets |
Create one. Only brand_id is required |
GET /v1/widgets/{id} |
One widget, with its snippet |
PATCH /v1/widgets/{id} |
Change settings. Within fields, only the fields given change |
DELETE /v1/widgets/{id} |
Delete it. Its document goes within seconds; pages keep an empty box |
POST /v1/widgets/preview |
The document a draft would publish, without saving anything |
curl -X POST -H "$AUTH" -H 'content-type: application/json' \
https://api.akteora.com/v1/widgets \
-d '{ "brand_id": "brd_…", "layout": "carousel", "fields": { "date": true } }'
The response includes snippet, height, script_url, document_url, and published_at:
null until the first publication a few seconds after creation.
The document
If you would rather draw testimonials yourself, read the widget's document_url. It is
public, cached, has no credentials and no expiry, and is served with
Access-Control-Allow-Origin: *.
{
"version": 1,
"widget_id": "wgt_01J8Z3Y4X5W6V7T8S9R0Q1P2N3",
"generated_at": "2026-09-13T12:00:00.000Z",
"layout": "carousel",
"theme": "auto",
"accent_color": "#0f766e",
"fields": { "avatar": true, "name": true, "title": true, "company": true, "rating": true, "date": false },
"attribution": true,
"brand_name": "Northwind",
"rating": { "average": 4.8, "count": 12 },
"items": [
{
"kind": "video",
"name": "Maria Garcia",
"title": "Head of Operations",
"company": "Northwind",
"rating": 5,
"date": null,
"text": "It halved our onboarding time.",
"video_url": "https://cdn.akteora.com/m/7kq2m9xwzr4tn8vbh3jp/video.mp4",
"audio_url": null,
"poster_url": "https://cdn.akteora.com/m/7kq2m9xwzr4tn8vbh3jp/poster.jpg",
"duration_ms": 42000,
"language": "en",
"captions": [{ "start_ms": 0, "end_ms": 1800, "text": "It halved our onboarding time." }]
}
]
}
What it promises:
versionchanges only for a breaking change. Check it, and ignore fields you do not know.kindisvideo,audioortext. A written testimonial hastextand no media.textis the respondent's own words: typed, or what was said in the recording.- Media URLs are the permanent public addresses described in Media.
captionsare timed from the recording's transcript; null when there is none.- A deleted widget's document returns
404.