> 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

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, a per-tenant configuration service, or a backend that vends short-lived tokens.

### Prerequisites

* A working Convai scene with a `ConvaiManager` component on a GameObject
* All examples create a subclass of `ConvaiManager` — add the new component to that same GameObject and remove the original `ConvaiManager` component

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.

### 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.                        |

{% hint style="danger" %}
`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.
{% endhint %}

{% 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. Because `ConvaiBootstrapConfigSnapshot` must be ready before `ConvaiManager.Awake()` calls `BuildRuntime()`, credentials must be fetched asynchronously before `base.Awake()` runs.

```csharp
// VaultCredentialManager.cs
using System.Threading.Tasks;
using Convai.Runtime.Components;
using Convai.Runtime.Core;
using Convai.Runtime.Core.Configuration;
using UnityEngine;

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

    private string _resolvedApiKey;
    private string _resolvedServerUrl = "https://live.convai.com";

    protected override async void Awake()
    {
        await FetchCredentialsAsync();
        base.Awake(); // Triggers BuildRuntime() → CreateRuntimeBuilder() with resolved credentials.
    }

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

    protected override ConvaiRuntimeBuilder CreateRuntimeBuilder()
    {
        ConvaiRuntimeBuilder builder = base.CreateRuntimeBuilder();
        builder.UseConfig(new ConvaiBootstrapConfigSnapshot(_resolvedApiKey, _resolvedServerUrl));
        return builder;
    }

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

`base.Awake()` is called explicitly after the credential fetch. Any code in other `Awake()` methods that depends on `ConvaiManager.ActiveManager` being ready must use `Start()` or later instead.

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

Multi-tenant deployments where each customer has a different API key can resolve credentials from a tenant config endpoint loaded at scene start.

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

public class TenantCredentialManager : ConvaiManager
{
    [SerializeField] private TenantConfigService _configService;

    private string _apiKey;
    private string _serverUrl = "https://live.convai.com";

    protected override async void Awake()
    {
        TenantConfig config = await _configService.LoadAsync();
        _apiKey    = config.ConvaiApiKey;
        _serverUrl = config.ConvaiServerUrl ?? _serverUrl;
        base.Awake();
    }

    protected override ConvaiRuntimeBuilder CreateRuntimeBuilder()
    {
        ConvaiRuntimeBuilder builder = base.CreateRuntimeBuilder();
        builder.UseConfig(new ConvaiBootstrapConfigSnapshot(_apiKey, _serverUrl));
        return builder;
    }
}
```

### Troubleshooting

| Symptom                                                             | Likely cause                                              | Fix                                                                                                            |
| ------------------------------------------------------------------- | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `[ConvaiManager] Cannot start: adapter not initialized.` in Console | `BuildRuntime()` ran before credentials were resolved     | Ensure credential fetch completes before `base.Awake()` is called.                                             |
| 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.
