# Authentication

Compute the hourly HMAC-SHA256 token and set the two headers every Hostraha reseller request needs.

Every request to the reseller API sends two headers:

- `username` — the reseller client's WHMCS email (your **API Email Address** from Settings → API Details).
- `token` — a signed value derived from your API key that rotates every UTC hour.

You never send the API key itself. Instead you send a token computed from it.

## The token formula

The token is an HMAC-SHA256 signature, hex-encoded, then base64-encoded. In PHP:

```php
base64_encode(hash_hmac("sha256", "<api-key>", "<email>:<gmdate('y-m-d H')>"))
```

Read this carefully — the arguments are not where you might expect:

- The algorithm is **HMAC-SHA256**.
- The **message** being signed is your **API key**.
- The **key** is the string `"<email>:<timestamp>"`, where the timestamp is the current UTC time formatted as `yy-mm-dd HH` (two-digit year, two-digit month and day, hour `00`–`23`).
- `hash_hmac(..., $binary = false)` returns a **lowercase hex string**. That hex string is then **base64-encoded** to produce the final token.

Because the timestamp only includes the hour, a token is valid for the current UTC hour. Compute a fresh token per request, or at least once per hour.

<Warning>
  Keep your API key secret. Store it in an environment variable or secrets manager, never in client-side code or version control. Add your server IPs to the **Allowed IP Addresses** allowlist in Settings → API Details so only your infrastructure can call the API.
</Warning>

### Clock skew

Because the timestamp resolves to the hour, small clock differences are usually fine. But near the top of the hour, a server whose clock is a minute fast or slow can compute a token for the wrong hour and get rejected. Keep your server clock synced with NTP. If a call fails right around an hour boundary, retry — the next attempt will fall inside the correct hour.

### Cloudflare and the User-Agent

The endpoint sits behind Cloudflare. Requests sent with a default scripting User-Agent (for example `Python-urllib`) can be blocked with `403 error code: 1010`. Send a normal User-Agent. The snippets below all use clients that pass an acceptable User-Agent by default.

## Make an authenticated call

Each snippet computes the token and calls `GET /version`, which returns the API version string. All five were validated live against the sandbox and returned `"2.3.0"`. Replace `your-api-key` and `you@example.com` with your own credentials.

<CodeGroup>
```bash cURL
API_KEY="your-api-key"
EMAIL="you@example.com"
ENDPOINT="https://portal.hostraha.com/modules/addons/DomainsReseller/api/index.php"
TS=$(date -u +"%y-%m-%d %H")
TOKEN=$(printf '%s' "$API_KEY" \
  | openssl dgst -sha256 -hmac "$EMAIL:$TS" -hex \
  | sed 's/^.*= //' | tr -d '\n' | base64 -w0)
curl -s "$ENDPOINT/version" \
  -H "username: $EMAIL" \
  -H "token: $TOKEN"
```

```php PHP
<?php
$apiKey   = "your-api-key";
$email    = "you@example.com";
$endpoint = "https://portal.hostraha.com/modules/addons/DomainsReseller/api/index.php";

$token = base64_encode(hash_hmac("sha256", $apiKey, "{$email}:" . gmdate("y-m-d H")));

$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, "{$endpoint}/version");
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HTTPHEADER, [
    "username: {$email}",
    "token: {$token}",
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
```

```python Python
from datetime import datetime, timezone

API_KEY  = "your-api-key"
EMAIL    = "you@example.com"
ENDPOINT = "https://portal.hostraha.com/modules/addons/DomainsReseller/api/index.php"

def token() -> str:
    ts = datetime.now(timezone.utc).strftime("%y-%m-%d %H")
    digest = hmac.new(f"{EMAIL}:{ts}".encode(), API_KEY.encode(), hashlib.sha256).hexdigest()
    return base64.b64encode(digest.encode()).decode()

resp = requests.get(f"{ENDPOINT}/version",
                    headers={"username": EMAIL, "token": token()})
print(resp.json())
```

```javascript Node.js

const API_KEY  = "your-api-key";
const EMAIL    = "you@example.com";
const ENDPOINT = "https://portal.hostraha.com/modules/addons/DomainsReseller/api/index.php";

function token() {
  const ts = new Date().toISOString().slice(2, 13).replace("T", " "); // "yy-mm-dd HH" UTC
  const digest = crypto.createHmac("sha256", `${EMAIL}:${ts}`).update(API_KEY).digest("hex");
  return Buffer.from(digest, "utf8").toString("base64");
}

const res = await fetch(`${ENDPOINT}/version`, {
  headers: { username: EMAIL, token: token() },
});
console.log(await res.json());
```

```go Go
package main

	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"encoding/hex"
	"fmt"
	"io"
	"net/http"
	"time"
)

const (
	apiKey   = "your-api-key"
	email    = "you@example.com"
	endpoint = "https://portal.hostraha.com/modules/addons/DomainsReseller/api/index.php"
)

func token() string {
	ts := time.Now().UTC().Format("06-01-02 15") // yy-mm-dd HH
	mac := hmac.New(sha256.New, []byte(email+":"+ts))
	mac.Write([]byte(apiKey))
	return base64.StdEncoding.EncodeToString([]byte(hex.EncodeToString(mac.Sum(nil))))
}

func main() {
	req, _ := http.NewRequest("GET", endpoint+"/version", nil)
	req.Header.Set("username", email)
	req.Header.Set("token", token())
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}
```
</CodeGroup>

Response:

```json
"2.3.0"
```

Reuse this token computation for every endpoint. The rest of the reference shows only the action path and parameters for each call.
