# Making Confidential Requests
Source: https://docs.chain.link/cre/guides/workflow/using-confidential-http-client/making-requests-ts
Last Updated: 2026-02-10


`ConfidentialHTTPClient` implements the [Confidential HTTP capability](/cre/capabilities/confidential-http-ts). Use it when an outbound call should carry [sensitive credentials or request fields](/cre/capabilities/confidential-http-ts#whats-kept-confidential) without assembling them as plain strings in workflow code on every node—see [when to use Confidential vs. regular HTTP](/cre/capabilities/confidential-http-ts#when-to-use-confidential-http-vs-regular-http). For those values, use **`vaultDonSecrets`** with **`{{.key}}`** placeholders only; **`runtime.getSecret()`** in headers or body follows a different trust boundary.

Unlike the regular [`HTTPClient`](/cre/reference/sdk/http-client), the Confidential HTTP client:

- Executes the request in a secure **enclave** (not on each node individually)
- Resolves **`vaultDonSecrets`** into the request via **Vault DON** template syntax
- Optionally **encrypts the response** before returning it to your workflow

## Prerequisites

This guide assumes you have:

- A basic understanding of CRE. If you are new, complete the [Getting Started tutorial](/cre/getting-started/overview) first.
- Familiarity with [secrets management](/cre/guides/workflow/secrets) in CRE.

## Step-by-step example

This example shows a workflow that makes a confidential GET request to an API, injecting a secret into the request headers using template syntax.

### Step 1: Configure your workflow

Add the API URL to your `config.json` file.

```json
{
  "schedule": "0 */5 * * * *",
  "url": "https://api.example.com/data",
  "owner": ""
}
```

### Step 2: Set up secrets for simulation

Confidential HTTP uses the `secrets.yaml` file. If you've already set up secrets for your project, you can reuse the same file. For a full walkthrough, see [Using Secrets in Simulation](/cre/guides/workflow/secrets/using-secrets-simulation).

Add the secrets your confidential request needs to your `secrets.yaml`:

```yaml
# secrets.yaml
secretsNames:
  myApiKey:
    - MY_API_KEY_ALL
```

Provide the actual value via an environment variable or `.env` file:

```bash
export MY_API_KEY_ALL="your-secret-api-key"
```

> **NOTE: Verify secrets-path in workflow\.yaml**
>
> Make sure the `secrets-path` field in your `workflow.yaml` points to your `secrets.yaml` file (for example,
> `"../secrets.yaml"` if it is at the project root). New projects created with the CLI may have this field empty by
> default.

### Step 3: Define your types

```typescript
import { z } from "zod"

const configSchema = z.object({
  schedule: z.string(),
  url: z.string(),
  owner: z.string(),
})

type Config = z.infer<typeof configSchema>

type TransactionResult = {
  transactionId: string
  status: string
}
```

### Step 4: Implement the request and wire it into your workflow

Unlike the regular HTTP client, the Confidential HTTP client takes a `Runtime` directly and does not require `runInNodeMode` wrapping:

```typescript
import { CronCapability, ConfidentialHTTPClient, handler, ok, json, type Runtime, Runner } from "@chainlink/cre-sdk"

const onCronTrigger = (runtime: Runtime<Config>): TransactionResult => {
  const confHTTPClient = new ConfidentialHTTPClient()

  // 1. Send the confidential request
  const response = confHTTPClient
    .sendRequest(runtime, {
      request: {
        url: runtime.config.url,
        method: "GET",
        multiHeaders: {
          Authorization: { values: ["Basic {{.myApiKey}}"] },
        },
      },
      vaultDonSecrets: [{ key: "myApiKey", owner: runtime.config.owner }],
    })
    .result()

  // 2. Check the response status
  if (!ok(response)) {
    throw new Error(`HTTP request failed with status: ${response.statusCode}`)
  }

  // 3. Parse and return the result
  return json(response) as TransactionResult
}
```

> **CAUTION: Do not assemble sensitive request data in workflow code**
>
> Register API credentials in the Vault DON, add them to **`vaultDonSecrets`**, and reference them **only** with **`{{.key}}` placeholders** in headers or body—not with plaintext from **`runtime.getSecret()`** or other workflow-built strings (those run in **Workflow DON** execution on every node). See [Using secrets with deployed workflows](/cre/guides/workflow/secrets/using-secrets-deployed).

With **multiple** secrets, list **`vaultDonSecrets`** keys in **alphabetical order** (Vault DON canonical ordering).

### Step 5: Simulate

Run the simulation:

```bash
cre workflow simulate
```

## Template syntax for secrets

Secrets are injected into request bodies and headers using Go template syntax: `{{.secretName}}`. The placeholder name must match the `key` in your `vaultDonSecrets` list.

**In the request body:**

```json
{ "auth": "{{.myApiKey}}", "data": "public-data" }
```

**In headers:**

```typescript
multiHeaders: {
  "Authorization": { values: ["Basic {{.myCredential}}"] },
}
```

The template placeholders are resolved inside the enclave. The actual secret values never appear in your workflow code or in node memory. Credentials must be wired through **`vaultDonSecrets`** as in Step 4—not interpolated from **`runtime.getSecret()`**.

## Response encryption

By default, the API response is returned unencrypted (`encryptOutput: false`). To encrypt the response body before it leaves the enclave, set `encryptOutput: true` and provide an AES-256 encryption key as a Vault DON secret.

### Setting up response encryption

1. **Store an AES-256 key** as a Vault DON secret with the identifier `san_marino_aes_gcm_encryption_key`:

   ```yaml
   # secrets.yaml
   secretsNames:
     san_marino_aes_gcm_encryption_key:
       - AES_KEY_ALL
   ```

The key must be a 256-bit (32 bytes) hex-encoded string:

```bash
export AES_KEY_ALL="your-256-bit-hex-encoded-key"
```

1. **Include the key in your `vaultDonSecrets`** and set `encryptOutput: true`:

   ```typescript
   const response = confHTTPClient
     .sendRequest(runtime, {
       request: {
         url: runtime.config.url,
         method: "GET",
         multiHeaders: {
           Authorization: { values: ["Basic {{.myApiKey}}"] },
         },
         encryptOutput: true,
       },
       vaultDonSecrets: [
         { key: "myApiKey", owner: runtime.config.owner },
         { key: "san_marino_aes_gcm_encryption_key" },
       ],
     })
     .result()
   ```

2. **Decrypt the response** in your own backend service. The encrypted response body is structured as `nonce || ciphertext || tag` and uses AES-GCM encryption.

> **CAUTION: Do not decrypt inside the workflow**
>
> The purpose of response encryption is to keep the response confidential even within the decentralized network. Decrypt
> the response in your own backend service, not inside the workflow itself.

## Response helper functions

The SDK response helpers `ok()`, `text()`, and `json()` work with Confidential HTTP responses just as they do with regular HTTP responses. For full documentation, see the [HTTP Client SDK Reference](/cre/reference/sdk/http-client-ts#helper-functions).

## Complete example

Here's the full workflow code for a confidential HTTP request with secret injection:

```typescript
import { CronCapability, ConfidentialHTTPClient, handler, ok, json, type Runtime, Runner } from "@chainlink/cre-sdk"
import { z } from "zod"

// Config schema
const configSchema = z.object({
  schedule: z.string(),
  url: z.string(),
  owner: z.string(),
})

type Config = z.infer<typeof configSchema>

// Result type
type TransactionResult = {
  transactionId: string
  status: string
}

// Main workflow handler
const onCronTrigger = (runtime: Runtime<Config>): TransactionResult => {
  const confHTTPClient = new ConfidentialHTTPClient()

  const response = confHTTPClient
    .sendRequest(runtime, {
      request: {
        url: runtime.config.url,
        method: "GET",
        multiHeaders: {
          Authorization: { values: ["Basic {{.myApiKey}}"] },
        },
      },
      vaultDonSecrets: [{ key: "myApiKey", owner: runtime.config.owner }],
    })
    .result()

  if (!ok(response)) {
    throw new Error(`HTTP request failed with status: ${response.statusCode}`)
  }

  const result = json(response) as TransactionResult
  runtime.log(`Transaction result: ${result.transactionId} — ${result.status}`)
  return result
}

// Initialize workflow
const initWorkflow = (config: Config) => {
  return [
    handler(
      new CronCapability().trigger({
        schedule: config.schedule,
      }),
      onCronTrigger
    ),
  ]
}

export async function main() {
  const runner = await Runner.newRunner<Config>({ configSchema })
  await runner.run(initWorkflow)
}
```

## API reference

For the full list of types and methods available on the Confidential HTTP client, see the [Confidential HTTP Client SDK Reference](/cre/reference/sdk/confidential-http-client-ts).