Skip to content

Recipe: Proxy Third-Party APIs

Call any external API from a Centrali function with server-side secret handling. This is the foundational pattern — every external integration (Stripe, OpenAI, Twilio, etc.) follows this structure.


Problem

Your frontend app needs to call an external API that requires a secret key. You can't expose the key in client-side code. You need a server-side proxy that holds the secret and forwards requests.

Solution

Create a Centrali function with an endpoint trigger. The frontend calls the endpoint, the function adds the secret and calls the external API, then returns the result.

Step 1: Create the function

async function run() {
  const { path, body } = executionParams.payload || {};
  const apiKey = triggerParams.apiKey;

  if (!apiKey) {
    return { success: false, error: 'API key not configured' };
  }

  try {
    const response = await api.httpPost(
      `https://api.example.com${path || '/v1/data'}`,
      body || {},
      {
        headers: {
          'Authorization': `Bearer ${apiKey}`,
          'Content-Type': 'application/json',
        },
      }
    );

    return { success: true, data: response };
  } catch (error) {
    api.log({ error: error.message, path });
    return { success: false, error: error.message };
  }
}

Step 2: Create an endpoint trigger

  1. In the Console, go to Functions → Triggers → New Trigger
  2. Select Endpoint as the trigger type
  3. Assign it to your function
  4. Set the static params — add your API key:
    { "apiKey": "sk_live_your_secret_key_here" }
    

Secrets in orchestrations

For stronger secret protection, wrap this function in an orchestration and use encrypted params instead of static trigger params. Encrypted params are stored with AES-256-GCM at rest and decrypted only at execution time.

Step 3: Call from your frontend

const result = await centrali.triggers.invokeEndpoint('my-proxy-trigger', {
  payload: {
    path: '/v1/chat/completions',
    body: { model: 'gpt-4', messages: [{ role: 'user', content: 'Hello' }] },
  },
});

Step 4: Add the external domain

In the Console, go to Logic → Domains and add api.example.com to the allowed domains list. Compute functions can only call domains that are explicitly allowed.


Adapting for specific providers

OpenAI

async function run() {
  const apiKey = triggerParams.openaiKey;
  const { messages, model } = executionParams.payload || {};

  const response = await api.httpPost(
    'https://api.openai.com/v1/chat/completions',
    { model: model || 'gpt-4', messages },
    { headers: { 'Authorization': `Bearer ${apiKey}` } }
  );

  return { success: true, data: response };
}

Stripe (create checkout session)

async function run() {
  const secretKey = triggerParams.stripeSecretKey;
  const { priceId, successUrl, cancelUrl } = executionParams.payload || {};

  const response = await api.httpPost(
    'https://api.stripe.com/v1/checkout/sessions',
    new URLSearchParams({
      'line_items[0][price]': priceId,
      'line_items[0][quantity]': '1',
      'mode': 'payment',
      'success_url': successUrl,
      'cancel_url': cancelUrl,
    }).toString(),
    {
      headers: {
        'Authorization': `Basic ${btoa(secretKey + ':')}`,
        'Content-Type': 'application/x-www-form-urlencoded',
      },
    }
  );

  return { success: true, data: { url: response.url, id: response.id } };
}

Common gotchas

  • Allowed domains: You must add the external API domain to your allowed domains list, otherwise the request will be blocked silently.
  • Timeouts: Compute functions have a default timeout. If the external API is slow, increase the trigger timeout or handle timeouts gracefully.
  • Error responses: External APIs return errors as HTTP responses, not exceptions. Check the response status before returning success: true.
  • Secret rotation: If you store secrets in trigger params, you update them in the Console. For orchestration encrypted params, update the orchestration step config.