---
seo_title: Embed a File Converter Widget on Your Website | EnConvert
meta_desc: Embed EnConvert's URL and file conversion widget on any website with one script tag. Turnstile auth via POST /v1/widget/{widget_id}/token, no API keys exposed.
keywords: embed file converter widget website, website file conversion widget, url to pdf widget embed, iframe file converter widget, embed pdf converter on website, no-code file conversion widget, wordpress file converter shortcode, widget embed script tag
---

# Embed a File Converter Widget on Your Website

Enconvert web widgets let you embed URL and file conversion directly into any website with a single script tag — visitors convert URLs to PDFs, capture screenshots, or convert uploaded files without leaving your page. Each widget authenticates through Cloudflare Turnstile challenges and short-lived JWTs issued by `POST /v1/widget/{widget_id}/token`, so no API key is ever exposed in your frontend code. Every conversion runs synchronously and returns a JSON response with a presigned download URL.

---

## 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](/docs/endpoints/web-pages/url-to-pdf), [url-to-screenshot](/docs/endpoints/web-pages/url-to-screenshot)
- File-based: All [data format](/docs/endpoints/data-formats), [document to PDF](/docs/endpoints/documents-to-pdf), and [image conversion](/docs/endpoints/image-conversions) endpoints

---

## How Widgets Work

### Setup

1. Go to your Enconvert **Dashboard > Widgets** and click **Create Widget**.
2. 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`).
3. 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:

```html
<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

1. **Widget loads** in the iframe and fetches its configuration from `GET /v1/widget/{widget_id}/config`.
2. **Domain validation** -- the widget verifies the parent page's origin matches the allowed domains list. Supports exact domains and wildcard subdomain patterns (`*.example.com`).
3. **User submits a URL or file** -- the widget requests an invisible Turnstile challenge token.
4. **Token exchange** -- the widget sends the Turnstile token to `POST /v1/widget/{widget_id}/token` and receives a JWT (1-hour expiry) plus a refresh token cookie (7-day expiry).
5. **Conversion** -- the widget calls the conversion endpoint with the JWT.
6. **Result** -- the API returns a JSON response with a `presigned_url`. The widget displays a download link.
7. **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}/refresh` using 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:**

```json
{
    "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:**

```json
{
    "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:**

```json
{
    "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:

```json
{
    "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

```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

```javascript
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. |

---

## Frequently asked questions

### How do I embed a file converter widget on my website?

Create a widget in the Enconvert dashboard (**Dashboard > Widgets > Create Widget**), then add one script tag to your page: `<script src="https://enconvert.com/embed.js" data-widget-id="your-widget-id"></script>`. If your site runs WordPress, you can use the `[enconvert_widget id="your-widget-id"]` shortcode from the Enconvert WordPress plugin instead.

### Do I need to expose an API key to embed a conversion widget?

No. The embed code contains only the widget ID — an internal public API key is auto-generated for each widget, restricted to its configured endpoint and allowed domains, and is never visible in your frontend code.

### How does the widget authenticate users without an API key?

The widget requests an invisible Cloudflare Turnstile challenge and exchanges it at `POST /v1/widget/{widget_id}/token` for a JWT with 1-hour expiry plus an httpOnly refresh token cookie with 7-day expiry. Subsequent conversions refresh the JWT via `POST /v1/widget/{widget_id}/refresh` with no new challenge, and the refresh token is rotated on every refresh.

### Can I restrict which domains can use my embedded widget?

Yes. Each widget has an allowed-domains list that supports exact domains and wildcard subdomains like `*.example.com`, validated server-side on token issuance, with `frame-ancestors` CSP headers restricting which pages can embed the iframe.

### How do I remove the Powered by Enconvert badge from the widget?

The badge is controlled by the `widget_branding` field on your subscription plan: Free and Starter plans display it, while Pro and Enterprise plans hide it. Upgrade to a Pro or Enterprise plan to remove the branding.
