Authenticating Custom Actions
Connect your agent to APIs that use API keys, bearer tokens, OAuth, client certificates, or signed assertions
Custom actions require a Builder plan or higher.
Upgrade to Builder →
Most APIs will not talk to you until you prove who you are. Chipp handles that for you: pick the authentication type your provider uses, point it at the credentials you have stored, and every call your agent makes is authenticated automatically.
Picking the right type
Open a custom action, go to the Auth tab, and choose the type that matches your provider’s documentation.
| Your provider gave you | Choose |
|---|---|
| An API key to put in a header | API Key Header |
A token to send as Authorization: Bearer ... | Bearer Token |
| A username and password (or a key used as the username) | Basic Auth |
| A client ID and client secret, plus a token URL | OAuth 2.0 (Client Credentials) |
| A client ID and a certificate, plus a token URL | OAuth 2.0, method Signed client assertion |
| A certificate and private key to install, no token URL | Client Certificate (mutual TLS) |
If you are unsure, search your provider’s API docs for “authentication”. The two certificate-based options are the giveaway: if they sent you a .pem, .pfx, or .crt file rather than a secret string, you need one of the bottom two rows.
Store credentials as variables, never inline
Every authentication type references credentials by variable name, not by pasting the value into the action.
- Open the Variables tab.
- Add a variable with type secret.
- Paste the value and save.
- On the Auth tab, reference it by name.
Secret variables are encrypted at rest and are never returned by the API, shown in the builder after saving, or included in what your agent’s model can see. The agent knows the credential exists and can use it; it cannot read it back or repeat it in a conversation.
API key, bearer, and basic auth
These three send a static credential in a request header.
Set Credential source to:
- Stored Application Variable for a key that belongs to you, shared by everyone using the agent. This is almost always what you want.
- Collected from the user each session when every end user brings their own key. The agent asks for it in conversation and uses it only for that session.
For API Key Header, set the header name your provider expects (X-API-Key, X-Auth-Token, and so on) and a value prefix if one is required, such as Token including the trailing space.
For Basic Auth, leave the username blank if your provider wants the API key itself as the username with an empty password. That is the pattern used by Follow Up Boss and several CRMs. Set a username only if your provider issued a real username and password pair.
OAuth 2.0 (client credentials)
Use this when your provider gives you a client ID, a client secret, and a token endpoint. Chipp fetches an access token, caches it, refreshes it before it expires, and retries once with a fresh token if the API ever rejects it. Do not build a separate “get a token” action; that work is already done for you.
You will need:
- Token URL: the
https://endpoint that issues tokens. - Client ID variable and Client secret variable: names of stored variables.
- Scope: space separated. Required more often than people expect. If your provider documents scopes and you omit them, you will receive a token that the API then rejects with a 401 or 403.
- Client authentication method: leave on Authorization header unless the provider specifically requires credentials in the request body.
OAuth 2.0 with a signed client assertion
Some providers refuse to issue a shared client secret at all. Instead, they register a certificate you own and expect you to prove you hold its private key on every token request. Microsoft Entra ID calls this certificate credentials; the standard is RFC 7523.
You have this flow if your provider gave you a client ID and a certificate, but no client secret.
Choose OAuth 2.0 (Client Credentials), then set Client authentication method to Signed client assertion. The client secret field disappears and two new fields replace it:
- Private key variable: a secret variable holding your PKCS#8 private key.
- Certificate variable: a variable holding the matching X.509 certificate.
Chipp signs a short-lived assertion with your key on every token request and includes the certificate’s thumbprint so the provider knows which registered key signed it.
The certificate is required, not optional. It is what tells the provider which of your registered keys to verify against. If you omit it, Microsoft Entra ID rejects the request with AADSTS700027: Client assertion contains an invalid signature, which sounds like a broken key but is really a missing certificate.
Client certificate (mutual TLS)
Use this when the API authenticates you during the TLS handshake instead of with a header. There is no token and no Authorization header at all. Your certificate is the credential. This is common in banking, healthcare, insurance, and freight and logistics APIs.
Provide two variables:
- Certificate variable: your X.509 certificate.
- Private key variable: the matching PKCS#8 private key, stored as a secret.
Two rules apply automatically:
- The action URL must be
https://. A client certificate over plain HTTP proves nothing. - Chipp will not follow a redirect to a different host. Presenting your certificate to a host you did not configure would be a credential leak, so point the action at the final URL directly.
Preparing your certificate and key
Both certificate-based options need the key in PKCS#8 format. Open the file in a text editor and look at the first line.
| First line of your key file | What to do |
|---|---|
-----BEGIN PRIVATE KEY----- | Ready to use. |
-----BEGIN RSA PRIVATE KEY----- | Convert it (PKCS#1). See below. |
-----BEGIN EC PRIVATE KEY----- | Convert it (SEC1). See below. |
-----BEGIN ENCRYPTED PRIVATE KEY----- | Decrypt it first. See below. |
To convert a key to PKCS#8:
openssl pkcs8 -topk8 -nocrypt -in key.pem -out key-pkcs8.pemTo decrypt a passphrase-protected key, run the same command; OpenSSL will prompt for the passphrase.
If your provider sent a .pfx or .p12 bundle, split it into a certificate and a key first:
openssl pkcs12 -in bundle.pfx -clcerts -nokeys -out cert.pem
openssl pkcs12 -in bundle.pfx -nocerts -nodes -out key.pem
openssl pkcs8 -topk8 -nocrypt -in key.pem -out key-pkcs8.pemPaste the full contents of each file, including the -----BEGIN----- and -----END----- lines, into its variable.
Certificates must be X.509 v3. Nearly every certificate issued by a provider or a real certificate authority already is. You will only see a v1 certificate if it was self-signed with an older OpenSSL command that omitted extensions; regenerate it with -addext "subjectAltName=DNS:yourhost".
Testing
Use Test Request inside the custom action editor. It runs through exactly the same authentication path as a live conversation, so a passing test means the credential works.
Common results and what they mean:
| What you see | What it usually means |
|---|---|
401 or 403 naming one of your variables | That credential is wrong, expired, or lacks permission. |
| A token error mentioning scope | Add the scopes your provider documents. |
| ”This key is in PKCS#1 format” | Convert the key as shown above. |
| ”This is an X.509 v1 certificate” | Reissue the certificate with v3 extensions. |
| ”The TLS layer rejected this certificate/key pair” | The certificate and key do not match each other, or one is truncated. Re-export both. |
| ”redirected to a different host” | Point the action at the final URL rather than a redirecting one. |
Configuring auth programmatically
Everything above is available through the Chipp MCP server, so an AI coding agent can configure authentication for you. Store the credential first, then reference it:
create_app_variable(app_id, name: "PARTNER_CLIENT_CERT", label: "Partner certificate",
type: "secret", value: "-----BEGIN CERTIFICATE-----...")
create_app_variable(app_id, name: "PARTNER_CLIENT_KEY", label: "Partner private key",
type: "secret", value: "-----BEGIN PRIVATE KEY-----...")
create_custom_action(app_id, name: "...", url: "https://api.example.com/v1/records",
method: "POST",
auth: {
type: "mtls",
certificateVariableName: "PARTNER_CLIENT_CERT",
privateKeyVariableName: "PARTNER_CLIENT_KEY"
})The same auth block is accepted by update_custom_action, and supports every type on this page.