> ## Documentation Index
> Fetch the complete documentation index at: https://rubie.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Embedded Credential Capture

> Embed Rubie's Blueprint picker and credential form in your product without handling user secrets.

The **Rubie Embedded Credential Capture widget** is an iframe flow for connecting
an end user's account without sending them away from your product. The widget
shows the Blueprints you make available, lets the user choose one, and renders
the credential fields required by that Blueprint.

Credentials travel directly from the iframe to Rubie. Your frontend receives
only lifecycle events, and your backend receives an opaque `credential_id` after
confirming the completed session.

## The shape of the flow

```mermaid theme={null}
sequenceDiagram
  participant Browser as Your frontend
  participant Backend as Your backend
  participant Rubie
  participant User as End user
  Browser->>Backend: Start connection flow
  Backend->>Rubie: POST /embedded-credential-sessions
  Rubie-->>Backend: ecs_... + embed_url
  Backend-->>Browser: embed_url
  Browser->>Rubie: Load embed_url in an iframe
  User->>Rubie: Choose Blueprint and submit credentials
  Rubie-->>Browser: rubie:credential-capture.completed
  Browser->>Backend: Confirm ecs_...
  Backend->>Rubie: GET /embedded-credential-sessions/ecs_...
  Rubie-->>Backend: completed + blueprint_key + cred_...
  Backend->>Backend: Store cred_... against the user and Blueprint
```

## Before you start

You'll need:

* The keys of the Blueprints you want to show in the widget
* An HTTPS origin where the iframe will be embedded
* A backend route that can call Rubie with your API key

Each Blueprint exposed in the widget must have a primary authentication strategy
configured in Rubie. That strategy determines the credential form shown after
the user selects the Blueprint. Your Rubie contact can configure the display
name, logo, description, and sort order used by the picker.

<Info>
  Blueprint keys are safe to use as frontend configuration, but your Rubie API
  key is not. Create and confirm embed sessions through your backend.
</Info>

## 1. Create an embed session

Create a new session when the user opens your connection flow:

```bash theme={null}
curl --fail-with-body -sS "$RUBIE_API_URL/api/v1/embedded-credential-sessions" \
  -X POST \
  -H "Authorization: Bearer $RUBIE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: connect-user-123" \
  -d '{
    "blueprint_keys": [
      "adp-worker-sync",
      "gusto-worker-sync",
      "bamboohr-worker-sync"
    ],
    "parent_origin": "https://app.example.com",
    "name": "Acme Corp HR connection"
  }'
```

```json theme={null}
{
  "id": "ecs_...",
  "status": "pending",
  "embed_url": "https://app.rubiehq.com/embed/credential-capture/...",
  "blueprint_keys": [
    "adp-worker-sync",
    "gusto-worker-sync",
    "bamboohr-worker-sync"
  ],
  "selected_blueprint_key": null,
  "credential_id": null,
  "parent_origin": "https://app.example.com",
  "expires_at": "2026-08-17T23:00:00.000Z",
  "completed_at": null,
  "created_at": "2026-08-17T22:45:00.000Z"
}
```

`blueprint_keys` is an explicit allowlist, not a catalog query. The widget never
shows other Blueprints from your Rubie account. If you pass one key, Rubie skips
the picker and opens that Blueprint's credential form directly.

`parent_origin` must be an exact HTTPS origin — scheme, host, and port, with no
path — allowlisted for your Rubie account. Rubie binds the session to that
origin and uses it as the target origin for browser events.

Embed sessions expire after **15 minutes**. Create them just in time rather than
when the surrounding page first loads. An `Idempotency-Key` replays the same
session for 24 hours, even after it expires, so use one per deliberate opening
of the widget rather than one permanent key per user.

## 2. Mount the iframe

Return only `id`, `embed_url`, and `expires_at` from your backend to the browser,
then set `embed_url` as the iframe source:

```html theme={null}
<iframe
  id="rubie-credential-capture"
  title="Connect an account"
  src="https://app.rubiehq.com/embed/credential-capture/..."
  style="width: 100%; min-height: 720px; border: 0"
  allow="clipboard-write"
></iframe>
```

Use a responsive container and give the iframe at least `640px` of width where
possible. On smaller screens, let it fill the viewport. The widget handles the
Blueprint grid, search, loading states, credential fields, and errors.

Avoid adding the `sandbox` attribute unless you have tested every authentication
strategy you expose. OAuth strategies may need forms, redirects, or popups that
an iframe sandbox blocks by default.

Your Content Security Policy must permit Rubie:

```text theme={null}
Content-Security-Policy: frame-src https://app.rubiehq.com;
```

Rubie applies a matching `frame-ancestors` policy for the session's
`parent_origin`. A copied `embed_url` cannot be framed by another origin.

## 3. Listen for widget events

The widget sends lifecycle events to its parent with `window.postMessage`:

```javascript theme={null}
const RUBIE_ORIGIN = "https://app.rubiehq.com";
const iframe = document.querySelector("#rubie-credential-capture");

window.addEventListener("message", async (event) => {
  if (event.origin !== RUBIE_ORIGIN) return;
  if (event.source !== iframe.contentWindow) return;

  const message = event.data;
  if (
    message?.source !== "rubie" ||
    message?.session_id !== currentEmbedSessionId
  ) {
    return;
  }

  switch (message.type) {
    case "rubie:credential-capture.ready":
      iframe.dataset.ready = "true";
      break;
    case "rubie:credential-capture.completed":
      await confirmConnection(message.session_id);
      break;
    case "rubie:credential-capture.cancelled":
      closeCredentialCapture();
      break;
    case "rubie:credential-capture.error":
      showConnectionError();
      break;
  }
});
```

Every message has this envelope:

```json theme={null}
{
  "source": "rubie",
  "type": "rubie:credential-capture.completed",
  "session_id": "ecs_..."
}
```

| `type`                               | Meaning                                                     |
| ------------------------------------ | ----------------------------------------------------------- |
| `rubie:credential-capture.ready`     | The widget is rendered and interactive                      |
| `rubie:credential-capture.completed` | Credentials were collected and the session can be confirmed |
| `rubie:credential-capture.cancelled` | The user closed the widget before completing                |
| `rubie:credential-capture.error`     | The widget could not continue; offer a retry                |

<Warning>
  Check `event.origin`, `event.source`, `source`, and `session_id` before acting
  on a message. A browser event is a notification, not proof of completion.
  Never accept a `credential_id` or Blueprint key from the browser.
</Warning>

## 4. Confirm the session

When the browser receives `rubie:credential-capture.completed`, ask your backend
to fetch the session:

```bash theme={null}
curl --fail-with-body -sS \
  "$RUBIE_API_URL/api/v1/embedded-credential-sessions/ecs_..." \
  -H "Authorization: Bearer $RUBIE_API_KEY"
```

```json theme={null}
{
  "id": "ecs_...",
  "status": "completed",
  "embed_url": null,
  "blueprint_keys": [
    "adp-worker-sync",
    "gusto-worker-sync",
    "bamboohr-worker-sync"
  ],
  "selected_blueprint_key": "adp-worker-sync",
  "credential_id": "cred_...",
  "parent_origin": "https://app.example.com",
  "expires_at": "2026-08-17T23:00:00.000Z",
  "completed_at": "2026-08-17T22:48:12.000Z",
  "created_at": "2026-08-17T22:45:00.000Z"
}
```

Only persist `credential_id` when `status` is `completed`. Store it with the
`selected_blueprint_key` and the user, workspace, or connection that initiated
the session. The selected key is guaranteed to be one of the session's original
`blueprint_keys`.

If the status is still non-terminal, poll every 2 seconds for a short period.
This leaves room for Rubie to add credential verification between submission
and completion. Stop on `completed` or `expired`.

| `status`    | Terminal? | What to do                                                    |
| ----------- | --------- | ------------------------------------------------------------- |
| `pending`   | No        | Keep the widget open or poll briefly after a completion event |
| `completed` | Yes       | Persist `credential_id` with `selected_blueprint_key`         |
| `expired`   | Yes       | Create a new session and reopen the widget                    |

Treat `status` as an open string set. Keep polling on unknown values, and only
consider a credential usable after `completed`.

## Closing and reopening

Closing your modal does not invalidate a pending session. You may reopen the
same `embed_url` until it completes or expires. Create a new session after
expiry, or when the user explicitly starts a different connection attempt.

The `cancelled` browser event means the user dismissed the widget; it does not
change the server-side session status. This keeps accidental closes recoverable.

## Reconnecting and revoking

To replace stale credentials, create a new embedded session and swap the stored
`credential_id` only after the new session completes. To disconnect, delete the
credential through [`DELETE /credentials/{id}`](/api-reference/credentials/delete-credential).

## Next

* [Hosted credential collection](/guides/hosted-credential-collection) — use a redirect or direct strategy instead of a Blueprint picker
* [Blueprints](/concepts/blueprints) — how Blueprints relate to strategies and runs
* [Triggering Blueprint runs](/guides/blueprint-runs) — use the captured credential
* [Idempotency](/guides/idempotency) — choose safe retry keys for session creation
