Saltar al contenido principal

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.

CredencialUso
api keyIdentifica su integración. Va en el encabezado Authorization de cada solicitud.
secret keyGenera 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>
  • Authorization identifica su integración y lleva la firma de la solicitud, calculada como se describe más abajo.
  • x-company-internal-code indica 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​

  1. Arme el texto firmado, con un dato por línea, separados por \n (salto de línea real, no los caracteres \ y n):
    1. método HTTP en mayúsculas (GET, POST, PUT, PATCH);
    2. path de la URL, sin host ni query string (/materials/boards); un path vacío vale /;
    3. parámetros de la query string ordenados por nombre (y, en nombres repetidos, por valor), cada uno como nombre=valor codificado como en el encodeURIComponent de JavaScript, unidos por &; sin query string, línea vacía;
    4. host:<host> seguido de una línea en blanco, donde <host> es exactamente el valor que su biblioteca HTTP envía en el encabezado host (el hostname, más :puerto solo cuando el puerto no es el predeterminado del protocolo);
    5. la palabra host, que es la lista de encabezados firmados;
    6. SHA-256 en hexadecimal de los bytes del cuerpo; sin cuerpo, el SHA-256 de la cadena vacía (e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855).
  2. Calcule el HMAC-SHA256 de ese texto con la secret key como clave.
  3. 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:

EjemploFirma
GETe2a240cea084ff4950cc78e1d48d6da563aae9f7935a8dfaa28c3672598ef344
POST8ac32656f8233168640afaff2f7c6213e42dd0fb9cdbd3f81b2ae666cab94e2b

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:

  • Authorization o x-company-internal-code falta o está mal formado;
  • la api key es desconocida o fue revocada;
  • la central indicada en x-company-internal-code no está vinculada a la api key o no está activa en Cortecloud;
  • signed-headers nombra 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.