API Proxies
API Proxies let your app call external APIs without exposing credentials to the browser. The proxy runs server-side โ API keys, OAuth tokens, and secrets never reach the client.
This is especially useful for client-facing components like journey blocks, portal blocks, and capabilities that need to call external APIs.
How it worksโ
Browser (your app component)
โ
โ proxy('my-api', '/products', { appId, token })
โ
โผ
epilot Proxy (server-side)
โ โ Injects auth credentials
โ โ Signs request with Ed25519
โ โ SSRF protection
โ
โผ
External API (https://api.example.com/products)
Quick startโ
1. Configure a proxy in the App Builderโ
Go to your app's Proxies section and add a target:
- Name โ a unique identifier (e.g.
products-api) - Target URL โ the base URL of the external API (must be HTTPS)
- Auth Type โ how to authenticate with the target API
2. Add secret options to your componentโ
If your proxy uses authentication, add a secret type option to your component under Configuration Options. The installer provides the actual API key or credentials when installing your app.
3. Call the proxy from your appโ
npm install @epilot/app-sdk
import { proxy } from '@epilot/app-sdk';
const response = await proxy(
'products-api', // proxy name (as configured in step 1)
'/products', // path on the target API
{
appId: 'your-app-id', // your app ID
token: ctx.token, // auth token from the app context
method: 'POST',
body: { category: 'solar' },
}
);
const data = await response.json();
Authentication typesโ
Noneโ
No credentials are injected. Useful for public APIs.
Custom Headerโ
Injects a secret value as a custom header (e.g. X-API-Key).
X-API-Key: <resolved-secret-value>
Bearer Tokenโ
Injects a secret value as a Bearer token.
Authorization: Bearer <resolved-secret-value>
OAuth 2.0 (Client Credentials)โ
The proxy handles the full OAuth 2.0 client credentials flow:
- Resolves
client_idandclient_secretfrom your component's options - Exchanges them at the token endpoint for an access token
- Caches the token until it expires
- Injects it as a Bearer header
Configure:
- Token URL โ the OAuth token endpoint (e.g.
https://auth.example.com/oauth/token) - Client ID โ reference to a component option
- Client Secret โ reference to a secret-type component option
- Scope โ optional, reference to a component option
Request signingโ
Every proxy request is signed with Ed25519 so the target API can verify it came from epilot. Three headers are added to every forwarded request:
| Header | Example |
|---|---|
webhook-id | msg_a1b2c3d4e5f6... |
webhook-timestamp | 1711360000 |
webhook-signature | v1a,base64... |
The signed content is {webhook-id}.{webhook-timestamp}.{body}.
Verifying signatures with the App SDKโ
The @epilot/app-sdk provides a verifyEpilotSignature helper to verify incoming requests:
import { verifyEpilotSignature } from '@epilot/app-sdk';
// Express / Node.js example
app.post('/webhook', async (req, res) => {
const isValid = await verifyEpilotSignature(req);
if (!isValid) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Process the verified request
res.json({ ok: true });
});
The SDK automatically fetches and caches the public key from https://cdn.app.sls.epilot.io/v1/.well-known/public-key.
tip
Make sure the raw request body is available as req.body (string or Buffer). If you're using a JSON body parser, configure it to also preserve the raw body โ the signature is verified against the raw body, not the parsed object.
Manual verificationโ
If you prefer to verify manually, fetch the public key and use Ed25519:
curl https://cdn.app.sls.epilot.io/v1/.well-known/public-key
{
"public_key": "-----BEGIN PUBLIC KEY-----\n...",
"algorithm": "ed25519",
"issuer": "epilot"
}
import { verify } from 'node:crypto';
const signedContent = `${webhookId}.${webhookTimestamp}.${rawBody}`;
const isValid = verify(
null,
Buffer.from(signedContent, 'utf8'),
publicKeyPem,
Buffer.from(signature, 'base64')
);
Securityโ
- Secrets are encrypted at rest with KMS โ never stored in plaintext
- Secret values are never returned to client-side components
- Only HTTPS targets are allowed
- SSRF protection validates target URLs before forwarding
- Requests require a valid epilot auth token and an active app installation