> For the complete documentation index, see [llms.txt](https://docs.convai.com/api-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.convai.com/api-docs/plugins-and-integrations/convai-unity-sdk/advanced-topics/custom-providers/custom-credential-provider.md).

# Custom credential provider

Override the SDK's default credential source to supply API keys from environment variables, a secrets vault, or any runtime-resolved credential store.

By default, the Convai Unity SDK reads the API key and server URL from `ConvaiSettings.asset`, stored in `Assets/Resources/`. Some deployment contexts require credentials to come from elsewhere — a CI environment variable, a secrets manager, or a per-tenant configuration service.

{% hint style="info" %}
If what you need is short-lived credentials resolved from your own server before each connection, the SDK has a built-in path for that and you do not need a custom credential provider. See [Authentication](/api-docs/plugins-and-integrations/convai-unity-sdk/authentication.md), and [Write a custom token provider](/api-docs/plugins-and-integrations/convai-unity-sdk/authentication/custom-token-provider.md) for the extension point it offers. Use the interface on this page for the cases that path does not cover — supplying a project API key from somewhere other than the settings asset.
{% endhint %}

### Prerequisites

* A working Convai scene with a `ConvaiManager` component on a GameObject
* The synchronous example (below) creates a subclass of `ConvaiManager` — add the new component to that same GameObject and remove the original `ConvaiManager` component. The asynchronous examples call `ConvaiManager.ActiveManager` from a plain component instead and need no subclass.

If you have not set up a scene yet, see [Getting Started](/api-docs/plugins-and-integrations/convai-unity-sdk/getting-started.md) first.

### How credentials flow through the SDK

When the runtime builds, `ConvaiBootstrapConfigSnapshot` captures the API key and server URL as an immutable pair. Once built, these values are exposed to modules and internal services via `ICredentialProvider`:

```csharp
public interface ICredentialProvider
{
    bool HasValidCredentials { get; }
    string GetApiKey();
    string GetServerUrl();
    void Refresh();
}
```

`GetApiKey()` and `GetServerUrl()` are called at connect time — not on every frame. `HasValidCredentials` gates connection attempts: if it returns `false`, the SDK will not attempt to connect. `Refresh()` is called when the SDK detects a credential-related error and wants the provider to reload from its source.

You do not implement `ICredentialProvider` directly. You supply credential values when building the runtime, and the SDK creates the provider internally from those values.

When a credential must be resolved asynchronously — a token fetched from your backend right before a connection attempt — the SDK resolves it through a second, additive interface, `IAsyncCredentialProvider`. Its `EnsureCredentialsAsync(CancellationToken)` method is awaited on the connect path, before `GetApiKey()` is read, so the SDK never needs an async `ConvaiManager` lifecycle hook for this case. You do not implement this interface directly either — see the asynchronous examples below and [Write a custom token provider](/api-docs/plugins-and-integrations/convai-unity-sdk/authentication/custom-token-provider.md) for the supported ways to supply a credential that must be fetched at connect time.

### Provide custom credentials

Override `CreateRuntimeBuilder()` on a `ConvaiManager` subclass and call `builder.UseConfig()` with a `ConvaiBootstrapConfigSnapshot` constructed from your credential source. Always call `base.CreateRuntimeBuilder()` first — it handles platform-specific transport selection, event system setup, and other wiring you do not need to replicate. Calling `UseConfig()` afterward overrides only the credential snapshot.

```csharp
// EnvironmentCredentialManager.cs
using Convai.Runtime.Components;
using Convai.Runtime.Core;
using Convai.Runtime.Core.Configuration;
using UnityEngine;

public class EnvironmentCredentialManager : ConvaiManager
{
    protected override ConvaiRuntimeBuilder CreateRuntimeBuilder()
    {
        ConvaiRuntimeBuilder builder = base.CreateRuntimeBuilder();

        string apiKey    = ResolveApiKey();
        string serverUrl = ResolveServerUrl();

        if (string.IsNullOrEmpty(apiKey))
        {
            Debug.LogError("[EnvironmentCredentialManager] API key not found. " +
                           "Check your environment or secrets configuration.");
        }

        builder.UseConfig(new ConvaiBootstrapConfigSnapshot(
            apiKey:    apiKey,
            serverUrl: serverUrl
        ));

        return builder;
    }

    private static string ResolveApiKey()
    {
        // Read from environment variable (CI, Docker, cloud run).
        string key = System.Environment.GetEnvironmentVariable("CONVAI_API_KEY");
        if (!string.IsNullOrEmpty(key)) return key;

        // Fall back to ConvaiSettings (editor / local dev).
        return ConvaiSettings.Instance?.ApiKey ?? string.Empty;
    }

    private static string ResolveServerUrl()
    {
        return System.Environment.GetEnvironmentVariable("CONVAI_SERVER_URL")
               ?? ConvaiSettings.Instance?.ServerUrl
               ?? "https://live.convai.com";
    }
}
```

In your Hierarchy, find the GameObject that has `ConvaiManager` on it. Add `EnvironmentCredentialManager` as a new component, then remove the original `ConvaiManager` component. The subclass inherits all `ConvaiManager` functionality — nothing else in the scene needs to change.

### ConvaiBootstrapConfigSnapshot parameters

`ConvaiBootstrapConfigSnapshot` is immutable — all values are set at construction and cannot change after the runtime starts.

| Parameter                  | Type                   | Default   | Description                                                            |
| -------------------------- | ---------------------- | --------- | ---------------------------------------------------------------------- |
| `apiKey`                   | `string`               | —         | **Required.** Your Convai API key.                                     |
| `serverUrl`                | `string`               | —         | **Required.** Convai realtime server URL.                              |
| `connectionType`           | `ConvaiConnectionType` | `Audio`   | Whether to connect with audio-only or audio + video.                   |
| `serverEndpoint`           | `ConvaiServerEndpoint` | `Connect` | Server endpoint variant. Leave as default unless directed otherwise.   |
| `connectionTimeoutSeconds` | `float`                | `30f`     | Timeout before a connect attempt is considered failed.                 |
| `globalLogLevel`           | `LogLevel`             | `Info`    | Initial SDK log level. Can be changed at runtime via `ConvaiSettings`. |
| `enableSessionResume`      | `bool`                 | `true`    | Whether the SDK should attempt to resume previous sessions.            |
| `maxRetryAttempts`         | `int`                  | `3`       | Maximum reconnection attempts before giving up.                        |

`ConvaiBootstrapConfigSnapshot` is captured at startup. If your credential source issues short-lived tokens, the SDK cannot automatically rotate them mid-session. Design your token lifetime to exceed the longest expected session, or disconnect and reconnect to apply a refreshed token.

{% hint style="danger" %}
Never log or serialize your API key to Unity's Console or a log file. `ConvaiBootstrapConfigSnapshot` intentionally omits the API key from its `ToString()` output.
{% endhint %}

### Usage examples

#### Example 1: Environment variable with local fallback

Shown above in [Provide custom credentials](#provide-custom-credentials). Best for CI/CD pipelines and Docker-based deployments where secrets are injected as environment variables.

#### Example 2: Secrets vault fetch before startup

Some deployments pull credentials from a secrets service at launch. `CreateRuntimeBuilder()` runs synchronously inside `ConvaiManager.Awake()`, before any `async` code can complete, so a `ConvaiManager` subclass cannot await the vault call there. Resolve the credential asynchronously in a plain component instead, then pass the resolved value straight to `ConvaiManager.ActiveManager.ConnectWithAuthTokenAsync()` — the same explicit-credential connect path documented in [Connect with an existing auth token](/api-docs/plugins-and-integrations/convai-unity-sdk/authentication/connect-with-auth-token.md). This requires Auth Token mode selected in Project Settings; see [Configure Auth Token mode](/api-docs/plugins-and-integrations/convai-unity-sdk/authentication/configure-auth-token-mode.md).

```csharp
// VaultSessionBootstrapper.cs
using System.Threading;
using System.Threading.Tasks;
using Convai.Runtime.Components;
using Convai.Runtime.Core.Async;
using UnityEngine;

public class VaultSessionBootstrapper : MonoBehaviour
{
    [SerializeField] private string _vaultEndpoint = "https://vault.internal/v1/convai";

    public async Task ConnectAsync(string endUserId, string endUserName, CancellationToken cancellationToken = default)
    {
        string credential = await FetchCredentialFromVaultAsync();
        if (string.IsNullOrEmpty(credential))
        {
            Debug.LogError("[VaultSessionBootstrapper] Vault returned no credential — connection aborted.");
            return;
        }

        try
        {
            await ConvaiManager.ActiveManager.ConnectWithAuthTokenAsync(
                credential, endUserId, endUserName, cancellationToken);
        }
        catch (ConvaiOperationException exception)
        {
            Debug.LogError($"[VaultSessionBootstrapper] Connection failed: {exception.Message}");
        }
    }

    private async Task<string> FetchCredentialFromVaultAsync()
    {
        using var client = new System.Net.Http.HttpClient();
        try
        {
            string json = await client.GetStringAsync(_vaultEndpoint);
            var response = JsonUtility.FromJson<VaultResponse>(json);
            return response.ApiKey;
        }
        catch (System.Exception ex)
        {
            Debug.LogError($"[VaultSessionBootstrapper] Failed to fetch credential: {ex.Message}");
            return null;
        }
    }

    [System.Serializable]
    private class VaultResponse { public string ApiKey; }
}
```

No `ConvaiManager` subclass is required — `ConnectWithAuthTokenAsync` accepts the resolved credential directly for that one connection attempt.

#### Example 3: Per-tenant credentials from a config service

Multi-tenant deployments where each customer has a different credential can resolve it from a tenant config endpoint before connecting, then pass the resolved value straight to `ConnectWithAuthTokenAsync()`.

```csharp
// TenantSessionBootstrapper.cs
using System.Threading;
using System.Threading.Tasks;
using Convai.Runtime.Components;
using Convai.Runtime.Core.Async;
using UnityEngine;

public class TenantSessionBootstrapper : MonoBehaviour
{
    [SerializeField] private TenantConfigService _configService;

    public async Task ConnectAsync(string endUserId, string endUserName, CancellationToken cancellationToken = default)
    {
        TenantConfig config = await _configService.LoadAsync();

        try
        {
            await ConvaiManager.ActiveManager.ConnectWithAuthTokenAsync(
                config.ConvaiCredential, endUserId, endUserName, cancellationToken);
        }
        catch (ConvaiOperationException exception)
        {
            Debug.LogError($"[TenantSessionBootstrapper] Connection failed: {exception.Message}");
        }
    }
}
```

As with the vault example, this requires Auth Token mode selected in Project Settings. If every connection in the scene should resolve the tenant credential automatically without touching call sites, register an `IConvaiAuthTokenProvider` instead — see [Write a custom token provider](/api-docs/plugins-and-integrations/convai-unity-sdk/authentication/custom-token-provider.md).

### Troubleshooting

| Symptom                                                    | Likely cause                                                                                                                                                | Fix                                                                                                              |
| ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `Cannot start: adapter not initialized.` in Console        | `StartRuntimeAsync()` was called before `ConvaiManager.Awake()` finished building the runtime and adapter — for example, a custom script called it directly | Let `ConvaiManager`'s built-in lifecycle call `StartRuntimeAsync()`. Do not invoke it manually before `Start()`. |
| Session connects but Convai returns auth error immediately | Empty or incorrect API key passed to snapshot                                                                                                               | Log the resolved key **length** (not the value) to confirm it was populated before build.                        |
| `IsValid` returns `false` on config snapshot               | `apiKey` or `serverUrl` is null or empty                                                                                                                    | Add a null check and fallback in your resolve methods.                                                           |
| `ConvaiSettings.Instance` is null in builds                | `ConvaiSettings.asset` not present in `Assets/Resources/`                                                                                                   | Only use `ConvaiSettings.Instance` as a fallback in editor/dev; never as the sole source in production builds.   |

### Next steps

{% content-ref url="/pages/ab23b641f90fca90f9440603f9ec5293143b86e4" %}
[Custom identity provider](/api-docs/plugins-and-integrations/convai-unity-sdk/advanced-topics/custom-providers/custom-identity-provider.md)
{% endcontent-ref %}

{% content-ref url="/pages/ed2429f37bbe856909dff42ad46c8c9e20800e6c" %}
[Custom persistence provider](/api-docs/plugins-and-integrations/convai-unity-sdk/advanced-topics/custom-providers/custom-persistence-provider.md)
{% endcontent-ref %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.convai.com/api-docs/plugins-and-integrations/convai-unity-sdk/advanced-topics/custom-providers/custom-credential-provider.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
