---
title: "How Authentication Works"
description: "Keys, request signing, and the per-request HMAC secret-key."
canonical: "https://eps.eko.in/docs/how-auth-works"
---


> **Canonical URL:** https://eps.eko.in/docs/how-auth-works
> This is a machine-readable Markdown version of the page for AI agents and LLMs. The primary (HTML) version lives at the canonical URL above.

# How Authentication Works

Every Eko API request is authenticated with a static key plus a per-request
HMAC signature. Nothing secret ever travels on the wire in plain form.

## The two keys

| Key                                     | Where it comes from                                                                           | How it's used                                             |
| --------------------------------------- | --------------------------------------------------------------------------------------------- | --------------------------------------------------------- |
| **Access Key _(also called Auth Key)_** | Shared once, kept server-side only. Get UAT key from your dashboard; production key after KYC | Never sent in a request; used to compute the `secret-key` |
| **Developer Key**                       | Get UAT key from your dashboard; production key after KYC                                     | Sent as the `developer_key` header                        |

## Request headers

| Header                 | Required | Description                                            |
| ---------------------- | -------- | ------------------------------------------------------ |
| `developer_key`        | yes      | Your static developer key.                             |
| `secret-key`           | yes      | Per-request HMAC signature (see below).                |
| `secret-key-timestamp` | yes      | Current time in **milliseconds** since the UNIX epoch. |
| `content-type`         | yes      | `application/json`                                     |

## Computing the `secret-key`

The signature binds the request to a timestamp, so it must be generated fresh
for every call:

1. Base64-encode the `access_key` — keep this as a **string**.
2. Take the current timestamp in milliseconds, as a string.
3. Compute `HMAC-SHA256` with the **timestamp as the message** and that
   **base64 string as the key**.
4. Base64-encode the result — that's the `secret-key`.

Send the same timestamp in `secret-key-timestamp`. A stale or mismatched
timestamp returns `403 Forbidden`.

```
secret-key = base64( HMAC-SHA256( message = timestamp, key = base64(access_key) ) )
```

> **Common mistake:** the HMAC key is the base64 **string** `base64(access_key)`,
> used as-is. Do **not** base64-decode it back to raw bytes before signing — that
> produces a different signature and a `403`.

## Code examples

Sign on your **backend** — the `access_key` must never reach a browser. The same
recipe in five languages:

```javascript
// Backend only. Never expose access_key in a browser.
import crypto from "node:crypto";

const accessKey = process.env.EKO_ACCESS_KEY;
const timestamp = Date.now().toString();
const encodedKey = Buffer.from(accessKey).toString("base64");
const secretKey = crypto
	.createHmac("sha256", encodedKey)
	.update(timestamp)
	.digest("base64");
// headers: { developer_key, "secret-key": secretKey, "secret-key-timestamp": timestamp }
```

## Try it: `secret-key` playground

Paste an access key and a timestamp to see the exact `secret-key` those inputs
produce — or paste a signature your own code generated and check it against
ours to find out why a request is coming back `403`.

> **Interactive secret-key playground** — available on the HTML version of this page (https://eps.eko.in/docs/how-auth-works). It computes and verifies the signature in the browser, so it has no markdown equivalent.

> This playground signs entirely inside your browser with the Web Crypto API —
> your access key is never sent to Eko or stored anywhere. That is safe for a
> scratchpad, but not a pattern to copy: in a real integration the `access_key`
> must stay on your backend, because anything shipped to a browser is public.
