Web Widgets
Web widgets allow you to embed Enconvert's URL and file conversion capabilities directly into any website. Visitors can convert URLs to PDFs, capture screenshots, or convert uploaded files without leaving your page. No API key is exposed in the frontend code.
Overview
Widgets are configured through the Enconvert dashboard and embedded as iframes via a lightweight script tag. Each widget is tied to a single conversion endpoint and a list of allowed domains. Authentication is handled automatically using Cloudflare Turnstile challenges, short-lived JWT tokens, and automatic token refresh.
Supported conversion endpoints:
Any Enconvert conversion endpoint can be used with widgets, including:
- URL-based: url-to-pdf, url-to-screenshot
- File-based: All data format, document to PDF, and image conversion endpoints
How Widgets Work
Setup
- Go to your Enconvert Dashboard > Widgets and click Create Widget.
- Select the conversion endpoint (e.g.,
/v1/convert/url-to-pdf) and specify the domains where the widget will be embedded. Wildcard subdomains are supported (e.g.,*.example.com). - An internal public API key is automatically created for the widget, restricted to the selected endpoint and allowed domains. This key is never exposed.
Embedding
Add the embed script to your website:
<script src="https://enconvert.com/embed.js" data-widget-id="your-widget-id"></script>
The script creates a sandboxed iframe that loads the Enconvert widget. No API key appears in the embed code.
Runtime Flow
- Widget loads in the iframe and fetches its configuration from
GET /v1/widget/{widget_id}/config. - Domain validation -- the widget verifies the parent page's origin matches the allowed domains list. Supports exact domains and wildcard subdomain patterns (
*.example.com). - User submits a URL or file -- the widget requests an invisible Turnstile challenge token.
- Token exchange -- the widget sends the Turnstile token to
POST /v1/widget/{widget_id}/tokenand receives a JWT (1-hour expiry) plus a refresh token cookie (7-day expiry). - Conversion -- the widget calls the conversion endpoint with the JWT.
- Result -- the API returns a JSON response with a
presigned_url. The widget displays a download link. - Timeout recovery -- if the conversion exceeds reverse-proxy timeout limits, the widget polls
GET /v1/convert/status/{job_id}using the pre-generated job ID.
Automatic Token Refresh
The widget never stops working due to expired authentication:
- On the initial conversion, the API issues both a JWT (1-hour expiry) and a refresh token (7-day expiry, httpOnly cookie).
- On subsequent conversions, the widget first attempts to refresh the JWT via
POST /v1/widget/{widget_id}/refreshusing the refresh token cookie -- no Turnstile challenge required. - If the refresh token itself has expired (after 7 days of inactivity), the widget falls back to a new Turnstile challenge.
- The refresh token is rotated on every refresh -- each refresh issues a new 7-day cookie.
This means a widget visitor who converts every few days will never see a Turnstile challenge after the first one.
Widget Endpoints
Configuration
Retrieves the configuration for a specific widget. No authentication required.
GET /v1/widget/{widget_id}/config
Response:
{
"endpoint": "/v1/convert/url-to-pdf",
"input_type": "url",
"allowed_domains": ["https://example.com", "*.example.com"],
"turnstile_site_key": "1x00000000000000000000AA",
"widget_branding": true
}
| Field | Description |
|---|---|
endpoint |
The conversion endpoint this widget is configured to use. |
input_type |
"url" for URL-based endpoints, "file" for file upload endpoints. |
allowed_domains |
Domains authorized to embed this widget. Supports wildcards. |
turnstile_site_key |
Cloudflare Turnstile site key for bot verification. |
widget_branding |
Whether the "Powered by Enconvert" badge is displayed. Determined by the subscription plan. |
Token Exchange
Exchanges a Turnstile challenge token for a JWT. Sets a refresh token cookie.
POST /v1/widget/{widget_id}/token
X-Parent-Origin: https://your-website.com
Content-Type: application/json
{
"turnstile_token": "cloudflare-challenge-response-token"
}
Response:
{
"token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600
}
Also sets an httpOnly refresh_token cookie (7-day expiry, Secure, SameSite=none).
Token Refresh
Refreshes an expired JWT using the httpOnly refresh token cookie. No Turnstile challenge required.
POST /v1/widget/{widget_id}/refresh
X-Parent-Origin: https://your-website.com
No request body needed -- the refresh token is read from the cookie automatically.
Response:
{
"token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600
}
The refresh token cookie is rotated on each refresh (new 7-day cookie issued).
Error responses:
- 401 -- no refresh token cookie or refresh token expired
- 403 -- refresh token does not match the widget's project, or domain not authorized
- 404 -- widget not found or deactivated
Conversion Response
Both URL-based and file-based widget conversions return a consistent JSON response with a presigned download URL:
{
"presigned_url": "https://spaces.example.com/...",
"object_key": "env/files/{project_id}/url-to-pdf/example_20260405_123456789.pdf",
"filename": "example_20260405_123456789.pdf",
"file_size": 123456,
"conversion_time_seconds": 8.5,
"job_id": "client-generated-uuid"
}
The widget uses the presigned_url to display a download link. Presigned URLs expire after 15 minutes.
Widget conversion restrictions: - Single URL / single file only - Synchronous mode only (no async or batch) - No webhook callbacks or notification emails - Endpoint restricted to the one configured for the widget
Widget Branding
Plans that include widget branding display a small "Powered by Enconvert" badge at the bottom of the widget. This is controlled by the widget_branding field on the subscription plan:
| Plan | Branding |
|---|---|
| Free / Starter | Displayed |
| Pro / Enterprise | Hidden |
The branding badge links to https://enconvert.com and is styled to be unobtrusive -- small text below the widget form with reduced opacity.
To remove branding, upgrade to a Pro or Enterprise plan.
Widget Management
Widgets are managed through the Enconvert dashboard or the backend API:
| Operation | Endpoint | Description |
|---|---|---|
| Create | POST /api/widgets |
Creates a widget and auto-generates an internal public API key. |
| List | GET /api/widgets?project_id={id} |
Lists all active widgets for a project. |
| Get | GET /api/widgets/{id} |
Retrieves a single widget's details. |
| Update | PATCH /api/widgets/{id} |
Updates widget name, endpoint, or API key. |
| Delete | DELETE /api/widgets/{id} |
Soft-deletes the widget (sets active=false). |
Embed Code Reference
Standard HTML
<script src="https://enconvert.com/embed.js" data-widget-id="your-widget-id"></script>
The script:
- Creates a sandboxed iframe (allow-scripts allow-same-origin allow-forms allow-popups)
- Sets width: 100%, initial height 400px, no border
- Enables clipboard-write permission
- Uses lazy loading
- Listens for Enconvert:resize messages to auto-adjust height
WordPress Shortcode
If you use the Enconvert WordPress plugin, embed widgets using the shortcode:
[enconvert_widget id="your-widget-id"]
Style Customization
Customize the widget appearance via query parameters on the embed script URL or the iframe source:
| Parameter | CSS Variable | Description |
|---|---|---|
bg |
--w-bg |
Widget background color |
text |
--w-text |
Text color |
btn-bg |
--w-btn-bg |
Button background color |
btn-text |
--w-btn-text |
Button text color |
border |
--w-border |
Border color |
radius |
--w-radius |
Border radius |
input-bg |
--w-input-bg |
Input field background |
result-bg |
--w-result-bg |
Result area background |
error |
--w-error |
Error text color |
font |
--w-font |
Font family |
padding |
--w-padding |
Widget padding |
max-width |
--w-max-width |
Maximum widget width |
Iframe Communication
The widget communicates with the parent page via postMessage. Listen for these events on the parent page:
| Event Type | Data | Description |
|---|---|---|
Enconvert:ready |
-- | Widget has loaded and is ready. |
Enconvert:resize |
{ height: number } |
Widget content height changed. Use to resize the iframe. |
Enconvert:conversion:complete |
{ url: string, filename?: string } |
Conversion completed. url is the presigned download URL. |
Enconvert:conversion:error |
{ error: string } |
Conversion failed. |
Example: Listening for Events
window.addEventListener("message", function(e) {
if (!e.data || !e.data.type) return;
if (e.data.type === "Enconvert:conversion:complete") {
console.log("Conversion done:", e.data.data.url);
}
if (e.data.type === "Enconvert:conversion:error") {
console.error("Conversion failed:", e.data.data.error);
}
});
Security
| Layer | Protection |
|---|---|
| Domain whitelisting | Widget only functions on listed domains. Supports exact matches and wildcard subdomains. Server-side validation on token issuance. |
| Turnstile verification | Every initial token request requires a valid Cloudflare Turnstile challenge response. |
| Endpoint restriction | Each widget is locked to a single conversion endpoint via allowed_endpoints in the JWT. |
| Token expiry | JWT expires after 1 hour. Refresh token expires after 7 days. Both are rotated on refresh. |
| Refresh token security | httpOnly cookie with Secure and SameSite=none -- inaccessible to JavaScript, only sent over HTTPS. |
| CORS protection | API gateway validates the widget iframe origin on every request. |
| CSP frame-ancestors | Widget config and token endpoints set frame-ancestors headers restricting which domains can embed the iframe. |
| No exposed keys | Embed code contains only the widget ID. The internal API key is never visible. |