<!-- Source: https://docs.infraio.xyz/es/api-reference/authentication -->
<!-- Last updated: 2026-10-04 -->

# Autenticación

InfraIO Pay tiene **dos superficies de API** con modelos de auth
distintos. Elige la que coincida con quién llama:

| Superficie | Prefijo de ruta | Audiencia | Auth |
| --- | --- | --- | --- |
| **B2B del comerciante** | `/b2b/v1/*` | Tu servidor | Firma de petición HMAC-SHA256 |
| **Dashboard** | Usada por el dashboard del comerciante | Sesiones de navegador para el dashboard del comerciante | Bearer JWT |

Esta página cubre la superficie **B2B**: la que llamas desde tu
servidor con un par de claves de API. La superficie del dashboard la
usa el dashboard de comerciante de InfraIO Pay y no es una superficie de
integración pública.

Envía siempre la ruta completa, incluyendo el prefijo `/b2b`, y firma
esa misma ruta (ver abajo).

## Endpoints

| Entorno | Base URL |
| --- | --- |
| Test | `https://api-dev.infraio.xyz` |
| Live | `https://api.infraio.xyz` |

Mismo patrón de URL: el entorno se controla mediante el **prefijo de
la clave** (`pk_test_…` vs `pk_live_…`), no la URL.

## Par de claves

Obtienes dos valores del dashboard del comerciante (**Developers →
API keys → + Add key**):

- **Publishable key** (`pk_test_…` o `pk_live_…`): identifica tu
  cuenta. Se envía como `X-Client-ID`. Seguro de incrustar en tu
  bundle de navegador (el SDK ya lo hace).
- **Secret key** (`sk_test_…` o `sk_live_…`): la clave de firma HMAC.
  Solo servidor. Trátala como una contraseña de base de datos.

> **Important:**
>
> Si una clave secreta alguna vez aterriza en un bundle de navegador,
> un repo de git, una línea de log o un chat compartido,
> **revócala inmediatamente** desde el dashboard. La revocación es
> instantánea, sin ventana de solapamiento. Emite una nueva clave
> y redespliega.

## Firmar una petición

Cada llamada a `/b2b/v1/*` lleva tres cabeceras:

```http
X-Client-ID:  pk_live_70d0becbd7a53047b0dfe88a960d10ad
X-Timestamp:  1715990400
X-Signature:  9a8b7c6d…             (hex HMAC-SHA256)
```

La firma se calcula sobre una cadena canónica:

```
METHOD + "\n" + PATH + "\n" + TIMESTAMP + "\n" + BODY
```

- `METHOD`: verbo HTTP en mayúsculas (`POST`, `GET`, …).
- `PATH`: ruta de la petición **incluyendo el prefijo `/b2b`**, sin
  el host y **sin la query string** (p. ej.
  `/b2b/v1/checkout-sessions/quick`). El prefijo debe estar presente.
  Los parámetros de query **no** se
  firman: para un `GET …?cursor=…&limit=20`, firma solo la ruta, no
  la parte `?…`.
- `TIMESTAMP`: unix-segundos, como cadena decimal (p. ej.
  `"1715990400"`), coincidiendo exactamente con `X-Timestamp`.
- `BODY`: bytes crudos del request body. Cadena vacía para
  `GET`/`DELETE`.

Firma con HMAC-SHA256 con la clave **secreta** como llave, salida
**hex**:

**Node / TS**

```ts
import { createHmac } from "node:crypto";

function sign({ method, path, body, secret }: {
  method: string; path: string; body: string; secret: string;
}) {
  const timestamp = Math.floor(Date.now() / 1000).toString();
  const input = [method.toUpperCase(), path, timestamp, body].join("\n");
  const signature = createHmac("sha256", secret).update(input).digest("hex");
  return { timestamp, signature };
}
```

**Go**

```go
package infraio

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "strconv"
    "time"
)

func Sign(method, path, body, secret string) (timestamp, signature string) {
    timestamp = strconv.FormatInt(time.Now().Unix(), 10)
    input := method + "\n" + path + "\n" + timestamp + "\n" + body
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write([]byte(input))
    return timestamp, hex.EncodeToString(mac.Sum(nil))
}
```

**Python**

```python
import hmac, hashlib, time

def sign(method: str, path: str, body: str, secret: str):
    timestamp = str(int(time.time()))
    input_ = f"{method}\n{path}\n{timestamp}\n{body}"
    signature = hmac.new(
        secret.encode(), input_.encode(), hashlib.sha256
    ).hexdigest()
    return timestamp, signature
```

## ¿Por qué HMAC y no Bearer?

Una API Bearer-token simple envía tu único secreto por el cable en
cada petición. Cualquiera que capture un log de proxy con TLS
terminado obtiene las llaves de tu cuenta. La firma HMAC significa
que el secreto nunca viaja, solo su firma derivada, que es de un solo
uso (atada a esa petición exacta + ese minuto exacto).

El compromiso: calculas una firma para cada llamada. Todavía no hay
un SDK de servidor, pero el helper de arriba son unas 15 líneas por
lenguaje.

## Tolerancia de timestamp

La tolerancia es de **±5 minutos** (300 segundos). Una petición fuera
de esa ventana se rechaza con `401 invalid_signature`. Dos
implicaciones:

1. **Sincroniza el reloj de tu servidor** con NTP. Un cron de larga
   duración con un reloj desviado fallará intermitentemente.
2. **No precalcules y encoles firmas.** Si una petición se queda en
   una cola de reintento durante más de 5 min, su firma expira.

## Scopes de clave

Las claves secretas llevan uno o más de estos paquetes de scope:

| Scope | Uso previsto |
| --- | --- |
| `read` | Listar/leer órdenes, sesiones, reembolsos |
| `write_order` | Crear sesiones de checkout, órdenes |
| `write_refund` | Emitir reembolsos, emitir tokens de solicitud de reembolso |
| `webhook_manage` | Crear/actualizar/eliminar endpoints de webhook |

El dashboard emite una clave de "acceso total" por defecto (los
cuatro scopes). Puedes emitir una clave con scope restringido desde
**Developers → API keys → + Add key** y marcar solo los scopes que
necesite la integración.

> **Warning:**
>
> **Los scopes todavía no se aplican.** Los scopes se registran en la
> clave y se muestran en el dashboard, pero cualquier clave `sk_…`
> válida puede llamar a cualquier endpoint `/b2b/v1/*` de tu
> comerciante. No te apoyes en los scopes como límite de seguridad.
> Rota o revoca claves para restringir el acceso.

## Verificación fallida

Si la firma, `X-Client-ID` o el timestamp no son válidos, la petición
se rechaza con **401 `INVALID_SIGNATURE`** antes de llegar a la API.
Solo las peticiones `/b2b/v1/*` se firman de esta manera. Los webhooks
usan un esquema aparte (ver abajo).

## Qué sigue

- [Errores](https://docs.infraio.xyz/es/api-reference/errors): forma de la respuesta en 4xx/5xx.
- [Seguridad → Claves de API](https://docs.infraio.xyz/es/security/api-keys): rotación,
  revocación, qué hacer si una clave secreta se filtra.
- [Webhooks → Verificación de firma](https://docs.infraio.xyz/es/webhooks/signature-verification):
  usa un esquema HMAC *diferente* (cabecera `X-Signature: sha256=…`,
  firma `X-Timestamp + "." + raw_body`, más un `X-Signature-Prev`
  opcional durante la ventana de gracia de rotación de 24 horas).
  No mezcles los esquemas: comparten el algoritmo de hash pero los
  bytes firmados y la familia de secretos (`whsec_…` vs `sk_…`) son
  diferentes.
