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, and can be dragged with a mouse),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 heading above the testimonials
header adds a title, an optional line under it, and the brand's star rating and review count.
It is off until you turn it on, and the avatars layout never draws one: that layout is a
one-line summary already.
header setting |
Default | |
|---|---|---|
enabled |
false |
Draw the heading |
title |
"What our customers say" |
Up to 100 characters |
subtitle |
null |
Up to 200 characters; null or empty for none |
show_rating |
true |
The average, as stars and a number |
show_count |
true |
"Based on 112 reviews" beside a rating; "40 testimonials" without one |
title_color |
null |
#rrggbb; null follows the theme, on light and dark pages alike |
text_color |
null |
The line under the title and the count; null follows the theme |
As with fields, only the settings you send change. The rating and count cover every
approved, public testimonial of the brand, the same numbers as its public wall, not just the
ones this widget shows. A widget narrowed to five-star reviews still shows the brand's real
average.
Turning the heading on makes the snippet reserve 104px more, so paste the new snippet. An
old, shorter box still draws everything, with less room for the cards.
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 or turn its heading on or off.
- 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 },
"header": { "enabled": true, "subtitle": "Real words from real customers." } }'
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 },
"header": {
"title": "What our customers say",
"subtitle": "Real words from real customers.",
"title_color": null,
"text_color": null,
"rating": { "average": 4.7, "count": 112 },
"count": 112
},
"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.ratingcovers the testimonials initems;header.ratingcovers the whole brand.headeris null while the heading is off. A document published before headings existed has noheaderat all until its next rebuild, so treat a missing one as null.- A deleted widget's document returns
404.