Autenticación
Cada solicitud a la API se firma con HMAC-SHA256 en el esquema SB1-HMAC-SHA256. No hay login, sesión, cookie ni token que renovar: la misma secret key firma todas las llamadas hasta que se reemplace.
Credenciales
Serrabits le proporciona a su integración dos credenciales por entorno. Para obtenerlas, escriba a suporte@serrabits.com.br.
| Credencial | Uso |
|---|---|
| api key | Identifica su integración. Va en el encabezado Authorization de cada solicitud. |
| secret key | Genera la firma de cada solicitud. Nunca se envía: solo la firma va en la solicitud. |
La secret key debe quedar únicamente en su backend. No la coloque en código de frontend, en una app móvil ni en ningún lugar accesible desde el navegador del usuario: quien tenga la secret key puede hacer llamadas en nombre de su integración. Si se filtra, escriba a suporte@serrabits.com.br para revocarla y recibir una nueva.
Encabezados
Cada solicitud lleva dos encabezados:
Authorization: SB1-HMAC-SHA256 api-key="<api key>", signed-headers="host", signature="<firma>"
x-company-internal-code: <código de la central>
Authorizationidentifica su integración y lleva la firma de la solicitud, calculada como se describe más abajo.x-company-internal-codeindica la central sobre la que actúa la solicitud (ver código de la central). Este encabezado no entra en la firma.
Las solicitudes con cuerpo también envían Content-Type: application/json.
La firma cubre el método, el path, la query string, el host y el cuerpo. Alterar cualquiera de ellos después de firmar invalida la solicitud, y una solicitud capturada no puede reutilizarse con otro path, método o cuerpo.
Cómo calcular la firma
- Arme el texto firmado, con un dato por línea, separados por
\n(salto de línea real, no los caracteres\yn):- método HTTP en mayúsculas (
GET,POST,PUT,PATCH); - path de la URL, sin host ni query string (
/materials/boards); un path vacío vale/; - parámetros de la query string ordenados por nombre (y, en nombres repetidos, por valor), cada uno como
nombre=valorcodificado como en elencodeURIComponentde JavaScript, unidos por&; sin query string, línea vacía; host:<host>seguido de una línea en blanco, donde<host>es exactamente el valor que su biblioteca HTTP envía en el encabezadohost(el hostname, más:puertosolo cuando el puerto no es el predeterminado del protocolo);- la palabra
host, que es la lista de encabezados firmados; - SHA-256 en hexadecimal de los bytes del cuerpo; sin cuerpo, el SHA-256 de la cadena vacía (
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855).
- método HTTP en mayúsculas (
- Calcule el HMAC-SHA256 de ese texto con la secret key como clave.
- Use el resultado en hexadecimal en minúsculas como
<firma>.
La línea en blanco después de host:<host> forma parte del texto: la línea del host termina con su propio salto de línea, sumado al \n que separa las partes.
Ejemplo: GET
Texto firmado para GET /materials/boards?limit=10 en el host api.example.com, sin cuerpo:
GET
/materials/boards
limit=10
host:api.example.com
host
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
Ejemplo: POST
Texto firmado para POST /services/123/production en el host api.example.com, con el cuerpo {"sellerEmail":"vendedor@example.com"}. El hash se calcula sobre los bytes exactos del cuerpo enviado: reformatear el JSON después de firmar (espacios, saltos de línea, orden de las claves) invalida la firma.
POST
/services/123/production
host:api.example.com
host
d4f22123a7a0685bd98bb9f7fc9b3db455e5b4e0c4420f0e0ddedd4826a3e0cc
Sin query string, la tercera línea queda vacía.
Valide su implementación
Con la secret key de prueba sb_sk_exemplo, los dos textos anteriores producen estas firmas:
| Ejemplo | Firma |
|---|---|
| GET | e2a240cea084ff4950cc78e1d48d6da563aae9f7935a8dfaa28c3672598ef344 |
| POST | 8ac32656f8233168640afaff2f7c6213e42dd0fb9cdbd3f81b2ae666cab94e2b |
Si su implementación llega a los mismos valores, está armando el texto como la API lo espera.
Código de referencia
Node.js
const crypto = require('crypto');
function canonicalQueryString(url) {
const entries = [...url.searchParams.entries()].sort(([ak, av], [bk, bv]) =>
ak === bk ? av.localeCompare(bv) : ak.localeCompare(bk),
);
return entries.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join('&');
}
function buildCanonicalRequest(method, url, body = '') {
const payloadHash = crypto.createHash('sha256').update(body, 'utf-8').digest('hex');
return [
method.toUpperCase(),
url.pathname || '/',
canonicalQueryString(url),
`host:${url.host}\n`,
'host',
payloadHash,
].join('\n');
}
const url = new URL('https://api.example.com/materials/boards?limit=10');
const canonicalRequest = buildCanonicalRequest('GET', url);
const signature = crypto.createHmac('sha256', secretKey).update(canonicalRequest).digest('hex');
const authorization = `SB1-HMAC-SHA256 api-key="${apiKey}", signed-headers="host", signature="${signature}"`;
Python
# urllib.parse.quote con safe="!*'()" reproduce el mismo conjunto
# de caracteres que el encodeURIComponent de JavaScript deja sin escapar.
import hashlib
import hmac
import urllib.parse
def encode(value):
return urllib.parse.quote(str(value), safe="!*'()")
def canonical_query_string(query_pairs):
return "&".join(
f"{encode(k)}={encode(v)}"
for k, v in sorted(query_pairs, key=lambda kv: (kv[0], kv[1]))
)
def build_canonical_request(method, path, query_pairs, host, body=b""):
payload_hash = hashlib.sha256(body).hexdigest()
return "\n".join([
method.upper(),
path or "/",
canonical_query_string(query_pairs),
f"host:{host}\n",
"host",
payload_hash,
])
canonical_request = build_canonical_request("GET", "/materials/boards", [("limit", "10")], "api.example.com")
signature = hmac.new(secret_key.encode(), canonical_request.encode(), hashlib.sha256).hexdigest()
authorization = f'SB1-HMAC-SHA256 api-key="{api_key}", signed-headers="host", signature="{signature}"'
No defina el encabezado host manualmente en la llamada HTTP: la biblioteca ya lo envía a partir de la URL. Solo asegúrese de que el valor usado para firmar (url.host y host en los ejemplos) sea exactamente el que ella envía; en la mayoría de las bibliotecas, es el mismo.
En las solicitudes con cuerpo, firme y envíe los mismos bytes. Serialice el JSON una sola vez, guarde la cadena y úsela tanto en el hash como en el cuerpo de la solicitud.
Cuando la autenticación falla
La API responde 401 cuando:
Authorizationox-company-internal-codefalta o está mal formado;- la api key es desconocida o fue revocada;
- la central indicada en
x-company-internal-codeno está vinculada a la api key o no está activa en Cortecloud; signed-headersnombra un encabezado que la solicitud no envió;- la firma no coincide con la solicitud recibida.
Para diagnosticar una firma que no coincide, compare el texto que usted firmó con el formato anterior, línea por línea: las causas más comunes son la falta de la línea en blanco después del host, la query string desordenada y el cuerpo reformateado entre el hash y el envío.