Integraciones SaaS con Claude Code: API keys, OAuth, webhooks y auditoría
Guía práctica para integrar SaaS con Claude Code: API keys, OAuth, webhooks, reintentos, secretos y logs de auditoría.
Cuando creas integraciones SaaS con Claude Code, la primera pregunta no es “qué endpoint llamo”. La primera pregunta es cómo autorizar de forma segura, cómo reintentar sin duplicar efectos, y cómo demostrar después quién hizo qué. Si saltas esa capa, incluso una notificación sencilla de Slack puede convertirse en un problema de seguridad, facturación u operación.
Esta guía aterriza API keys, OAuth, webhooks, límites de tasa, reintentos, idempotencia, logs de auditoría, secretos y entornos de prueba en un flujo práctico para Claude Code. Un webhook es un evento que el SaaS envía a tu servidor. Idempotencia significa que repetir una operación no genera un resultado duplicado. Un log de auditoría es el registro que permite explicar la automatización más tarde.
Los ejemplos usan Node.js 20 o superior y se pueden copiar en un directorio scripts/.
Arquitectura de integración
No conviertas a Claude Code en el único lugar donde vive el estado de la integración. Claude Code planifica, genera y ejecuta comandos; los tokens, el estado de reintentos y el historial de auditoría deben vivir en el código del conector.
flowchart LR
A[Claude Code] --> B[CLI or MCP connector]
B --> C[Auth and secret store]
B --> D[Retry and rate-limit wrapper]
D --> E[SaaS API]
E --> F[Webhook receiver]
F --> G[Queue]
G --> H[Worker]
H --> I[Audit log]
La versión mínima útil es un wrapper CLI, por ejemplo node scripts/slack-notify.mjs llamado desde Claude Code. Si el flujo se vuelve frecuente, conviene promoverlo a un servidor MCP para reutilizar esquema de entrada, permisos y manejo de errores. Los receptores webhook requieren más trabajo, pero son necesarios cuando Stripe, GitHub, Slack u otro SaaS inicia el proceso.
Cuatro casos de uso concretos
El primer caso es la operación de releases. Claude Code lee commits no publicados de GitHub, redacta release notes y publica un resumen compacto en Slack. Slack Incoming Webhooks se configuran rápido, pero no son la herramienta adecuada para borrar mensajes o crear flujos de chat complejos; ahí conviene pasar a la Slack Web API.
El segundo caso es automatización de facturación. Un webhook de Stripe recibe checkout.session.completed, registra el cliente en una herramienta interna y manda los fallos a una cola de reintento. Aquí son obligatorios la verificación de firma, las claves de idempotencia y la separación estricta entre modo de prueba y modo live.
El tercer caso es triage de soporte. Google Workspace OAuth permite que un backend lea un CSV de soporte; Claude Code clasifica las filas; y se crean issues de GitHub para seguimiento de ingeniería. Como el flujo toca datos de usuarios, OAuth con scopes limitados de solo lectura es más seguro que una API key compartida.
El cuarto caso es un panel de auditoría. Cada acción SaaS disparada por Claude Code se escribe como NDJSON para que el equipo vea actor, proveedor, acción, destino y clave de idempotencia. Esto sirve incluso antes de implantar una plataforma formal de auditoría.
Elegir API key, OAuth, webhook o conector
| Método | Explicación simple | Mejor para | Cuidado con |
|---|---|---|---|
| API key | Credencial compartida del servidor | Llamadas de servidor a Stripe, avisos internos en Slack | Guardarla en variables de entorno o secret manager, nunca en código |
| OAuth | Un usuario o workspace concede acceso | Google Drive, GitHub Apps, acciones por usuario | Refresh tokens y scopes requieren diseño explícito |
| Webhook | El SaaS envía un evento a tu endpoint | Pagos de Stripe, issues de GitHub, eventos de Slack | Verificar firmas, tolerar duplicados y no confiar en el orden |
| Conector CLI/MCP | Herramienta estable que Claude Code puede llamar | Runbooks, ops internas, flujos entre varios SaaS | Validar entradas y registrar logs dentro del conector |
La documentación de Google OAuth 2.0 explica el flujo de access token y refresh token. En GitHub y Slack también necesitas logs con actor, porque “lo hizo el bot” no basta cuando depuras permisos.
Checklist de variables de entorno
Empieza con .env.example y comparte solo nombres. Los valores reales no se suben a Git.
# .env.example
INTEGRATION_ENV=sandbox
SAAS_API_TOKEN=
SLACK_WEBHOOK_URL=
GITHUB_WEBHOOK_SECRET=
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
STRIPE_SECRET_KEY=sk_test_xxx
STRIPE_WEBHOOK_SECRET=whsec_xxx
AUDIT_LOG_PATH=logs/saas-audit.ndjson
# .gitignore
.env
.env.*
!.env.example
logs/
Antes de permitir que Claude Code ejecute la integración:
- Mantén claves live y de prueba en entornos separados.
- No pegues secretos en prompts de Claude Code.
- No imprimas
Authorizationni secretos de webhook en errores. - Empieza OAuth con scopes de solo lectura y amplía solo si hace falta.
- Registra valores de CI como secrets, no como variables públicas del repositorio.
- Guarda quién rotó una clave y en qué fecha.
Wrapper de reintentos y rate limits
Un rate limit es el ritmo máximo de peticiones que permite un proveedor. Las APIs REST de GitHub tienen límites primarios y secundarios; al fallar por límite debes respetar los headers. Slack responde con 429 Too Many Requests y Retry-After. Ese comportamiento debe estar en código, no en instrucciones improvisadas.
// scripts/saas-request.mjs
import crypto from "node:crypto";
import { pathToFileURL } from "node:url";
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
export async function saasRequest(url, options = {}) {
const {
method = "GET",
token = process.env.SAAS_API_TOKEN,
body,
idempotencyKey = method === "POST" ? crypto.randomUUID() : undefined,
maxRetries = 4,
headers = {},
} = options;
for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
const res = await fetch(url, {
method,
headers: {
Accept: "application/json",
...(body ? { "Content-Type": "application/json" } : {}),
...(token ? { Authorization: `Bearer ${token}` } : {}),
...(idempotencyKey ? { "Idempotency-Key": idempotencyKey } : {}),
...headers,
},
body: body ? JSON.stringify(body) : undefined,
});
if (res.ok) return res;
const retryAfter = Number(res.headers.get("retry-after"));
const shouldRetry = [408, 409, 425, 429, 500, 502, 503, 504].includes(res.status);
if (!shouldRetry || attempt === maxRetries) {
const text = await res.text();
throw new Error(`SaaS request failed: ${res.status} ${text.slice(0, 200)}`);
}
const backoffMs = Number.isFinite(retryAfter)
? retryAfter * 1000
: Math.min(30000, 500 * 2 ** attempt);
await sleep(backoffMs);
}
throw new Error("unreachable");
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
const url = process.argv[2];
if (!url) throw new Error("Usage: node scripts/saas-request.mjs <url>");
const res = await saasRequest(url);
console.log(await res.text());
}
En APIs como Stripe, que soportan claves de idempotencia, el mismo POST puede reintentarse con seguridad. Para tus workers, usa una clave como provider + event_id + action y omite el procesamiento si ya existe.
Flujo de verificación de webhooks
Los webhooks son entradas HTTP públicas. Sin verificación de firma, cualquiera puede enviar eventos falsos. GitHub usa X-Hub-Signature-256; Stripe usa Stripe-Signature. La regla clave es verificar el raw body antes de parsear JSON.
1. Leer el raw body.
2. Leer el header de firma.
3. Calcular HMAC con el secreto compartido.
4. Comparar con una comparación timing-safe.
5. Guardar el delivery id como clave de idempotencia.
6. Responder 202 rápido y procesar lo pesado en una cola.
// scripts/verify-github-webhook.mjs
import crypto from "node:crypto";
import http from "node:http";
const secret = process.env.GITHUB_WEBHOOK_SECRET;
if (!secret) throw new Error("Set GITHUB_WEBHOOK_SECRET");
function verifyGitHubSignature(rawBody, signatureHeader) {
const received = Array.isArray(signatureHeader)
? signatureHeader[0]
: signatureHeader ?? "";
const expected =
"sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
const receivedBytes = Buffer.from(received);
const expectedBytes = Buffer.from(expected);
return (
receivedBytes.length === expectedBytes.length &&
crypto.timingSafeEqual(receivedBytes, expectedBytes)
);
}
http
.createServer(async (req, res) => {
const chunks = [];
for await (const chunk of req) chunks.push(chunk);
const rawBody = Buffer.concat(chunks);
if (!verifyGitHubSignature(rawBody, req.headers["x-hub-signature-256"])) {
res.writeHead(401);
res.end("invalid signature");
return;
}
const event = req.headers["x-github-event"];
const delivery = req.headers["x-github-delivery"];
console.log(JSON.stringify({ event, delivery, receivedAt: new Date().toISOString() }));
res.writeHead(202, { "Content-Type": "application/json" });
res.end(JSON.stringify({ ok: true }));
})
.listen(3000, () => console.log("Listening on http://localhost:3000"));
Con Stripe, en producción es más seguro usar constructEvent() del SDK oficial. Los secretos de webhook de test y live son distintos, así que STRIPE_WEBHOOK_SECRET debe depender del entorno.
Logs de auditoría
Un log de auditoría es el registro que explica una acción automatizada más tarde. El historial de chat de Claude Code no basta; necesitas timestamp, actor, proveedor, acción, destino, clave de idempotencia y estado.
{
"ts": "2026-06-03T09:15:00.000Z",
"actor": "claude-code",
"provider": "github",
"action": "create_issue",
"target": "owner/repo#123",
"idempotencyKey": "github:issue:customer-42:2026-06-03",
"status": "succeeded"
}
// scripts/audit-log.mjs
import { appendFile, mkdir } from "node:fs/promises";
import { dirname } from "node:path";
export async function writeAudit(event) {
const record = {
ts: new Date().toISOString(),
actor: event.actor ?? "claude-code",
provider: event.provider,
action: event.action,
target: event.target,
idempotencyKey: event.idempotencyKey,
status: event.status ?? "started",
};
const file = process.env.AUDIT_LOG_PATH ?? "logs/saas-audit.ndjson";
await mkdir(dirname(file), { recursive: true });
await appendFile(file, `${JSON.stringify(record)}\n`, "utf8");
}
if (process.argv[1]?.endsWith("audit-log.mjs")) {
await writeAudit({
provider: "slack",
action: "post_message",
target: "#release",
idempotencyKey: "demo-2026-06-03",
status: "succeeded",
});
}
NDJSON basta para la primera versión. Luego puedes importarlo en BigQuery, DuckDB o una hoja de cálculo.
Errores frecuentes
Un fallo común es intentar entregar webhooks directamente a localhost. La documentación de GitHub indica que las URLs de webhook deben ser públicas. Usa un servicio de forwarding para pruebas locales y HTTPS en producción.
Otro error es confiar en el orden de los webhooks. Los proveedores pueden entregar eventos tarde o fuera de orden. Usa timestamps, delivery ids y el estado actual del recurso.
El tercer problema es duplicar trabajo al reintentar. Pagos, issues, posts de Slack y correos son visibles para usuarios. Añade claves de idempotencia a los POST y guarda delivery ids procesados en los workers.
El cuarto error es pedir scopes OAuth demasiado amplios. Empieza con solo lectura, un único folder o workspace, y tokens de vida corta. Amplía cuando puedas explicar el riesgo.
El quinto error es pegar secretos en Claude Code. Pide a Claude Code que use nombres de variables de entorno; los valores reales viven en secret manager, CI secret o .env local.
Cuándo crear una abstracción de conector
El primer script puede ser directo y aburrido. Cuando repitas autorización, paginación, rate limiting, auditoría y formato de errores en tres flujos, crea un conector.
Nombra la abstracción con lenguaje de negocio: sendMessage(), createTicket(), recordPaymentEvent(), writeAudit(). Un wrapper fino como callSlackApi() ayuda menos a Claude Code que una función que expresa el resultado esperado.
Para detalles cercanos, combina esto con Claude Code API Design Assistant y Claude Code API Testing. Para revisar riesgos, lee Claude Code Security Best Practices antes de dar acceso de producción.
Referencias oficiales
- Stripe: verificación de firma de webhooks
- Stripe: idempotent requests
- Slack: Incoming Webhooks
- Slack: Web API rate limits
- GitHub: validar entregas de webhooks
- GitHub: REST API rate limits
- Google: OAuth 2.0 for Google APIs
Si vas a llevar integraciones de Claude Code a flujos de equipo, prepara plantillas reutilizables antes del primer incidente. Las checklists prácticas están en /products/ y la formación para equipos en /training/.
Al probarlo en la práctica, las integraciones que incluyeron verificación de firma, idempotencia, logs de auditoría y entornos de prueba separados desde el primer día fueron mucho más fáciles de extender. Incluso una pequeña notificación de Slack se vuelve más fiable cuando los fallos se pueden rastrear y reproducir de forma deliberada.
PDF gratis: cheatsheet de Claude Code
Introduce tu email y descarga una hoja con comandos, hábitos de revisión y flujos seguros.
Cuidamos tus datos y no enviamos spam.
Sobre el autor
Masa
Ingeniero enfocado en workflows prácticos con Claude Code.
Artículos relacionados
Escalera de permisos de Claude Code para ampliar acceso sin perder control
Pasa de read-only a ediciones limitadas, comandos de prueba y checks de deploy con menos riesgo.
Claude Code Small PR Proof Pack: cambios pequeños que sí se pueden revisar
Un paquete de prueba para PRs de Claude Code: diff, checks, URL pública, CTA y rollback.
Gate de revisión antes del commit con Claude Code
Cómo revisar con Claude Code antes del commit: diff, build, URL pública, Gumroad, consultoría, tests y archivos ajenos.