API Reference v1.0

Introducción

Bienvenido a la documentación de InnovaAPI. Desde aquí podrás enviar mensajes de WhatsApp, gestionar tus API Keys y configurar Webhooks.

Autenticación

Todas las solicitudes requieren tu API Key en el header Authorization:

Authorization: Bearer TU_API_KEY
Importante: Tu API Key se genera desde el panel de control en la sección API Keys. Guárdala de forma segura — solo se muestra una vez al crearla.
URL Base
https://TU_SERVIDOR
Formato de respuesta

Todas las respuestas son JSON con la estructura:

{ "success": true/false, "data": {...}, "message": "..." }
Códigos de Error
CódigoSignificado
401API Key inválida o no enviada
400WhatsApp no conectado o parámetros incorrectos
404Recurso no encontrado
429Límite de mensajes diarios alcanzado
500Error interno del servidor

Enviar Mensaje

Envía mensajes de WhatsApp: texto, imagen, documento, video, audio o ubicación.

POST /api/whatsapp/enviar
Parámetros del Body
CampoTipoDescripción
numero requerido string Número con código de país. Ej: 573001234567
tipo requerido string text | image | document | video | audio | location
mensaje string Texto del mensaje. Requerido si tipo es text
url string URL del archivo. Requerido para image, document, video, audio
caption string Texto opcional debajo de la imagen o video
latitud number Latitud. Requerido para location
longitud number Longitud. Requerido para location
nombre string Nombre del lugar. Opcional para location
Ejemplos de Código
Texto
const res = await fetch('https://TU_SERVIDOR/api/whatsapp/enviar', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer TU_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    numero: '573001234567',
    tipo: 'text',
    mensaje: 'Hola, este es un mensaje de prueba'
  })
});
const data = await res.json();
// { success: true, message: 'Mensaje enviado correctamente' }
Imagen
body: JSON.stringify({
  numero: '573001234567',
  tipo: 'image',
  url: 'https://ejemplo.com/imagen.jpg',
  caption: 'Mira esta imagen'
})
Ubicación
body: JSON.stringify({
  numero: '573001234567',
  tipo: 'location',
  latitud: 4.7110,
  longitud: -74.0086,
  nombre: 'Bogotá, Colombia'
})
Texto
import requests

headers = {
    'Authorization': 'Bearer TU_API_KEY',
    'Content-Type': 'application/json'
}

res = requests.post('https://TU_SERVIDOR/api/whatsapp/enviar',
    headers=headers,
    json={
        'numero': '573001234567',
        'tipo': 'text',
        'mensaje': 'Hola, este es un mensaje de prueba'
    }
)
print(res.json())
Imagen
res = requests.post('https://TU_SERVIDOR/api/whatsapp/enviar',
    headers=headers,
    json={
        'numero': '573001234567',
        'tipo': 'image',
        'url': 'https://ejemplo.com/imagen.jpg',
        'caption': 'Mira esta imagen'
    }
)
Ubicación
res = requests.post('https://TU_SERVIDOR/api/whatsapp/enviar',
    headers=headers,
    json={
        'numero': '573001234567',
        'tipo': 'location',
        'latitud': 4.7110,
        'longitud': -74.0086,
        'nombre': 'Bogotá, Colombia'
    }
)
Texto
<?php
$ch = curl_init('https://TU_SERVIDOR/api/whatsapp/enviar');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer TU_API_KEY',
        'Content-Type: application/json'
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'numero'  => '573001234567',
        'tipo'    => 'text',
        'mensaje' => 'Hola, este es un mensaje de prueba'
    ])
]);
$res = json_decode(curl_exec($ch), true);
?>
Imagen
CURLOPT_POSTFIELDS => json_encode([
    'numero'  => '573001234567',
    'tipo'    => 'image',
    'url'     => 'https://ejemplo.com/imagen.jpg',
    'caption' => 'Mira esta imagen'
])
Texto
curl -X POST https://TU_SERVIDOR/api/whatsapp/enviar \
  -H "Authorization: Bearer TU_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"numero":"573001234567","tipo":"text","mensaje":"Hola mundo"}'
Imagen
curl -X POST https://TU_SERVIDOR/api/whatsapp/enviar \
  -H "Authorization: Bearer TU_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"numero":"573001234567","tipo":"image","url":"https://ejemplo.com/img.jpg","caption":"Texto"}'
Documento
curl -X POST https://TU_SERVIDOR/api/whatsapp/enviar \
  -H "Authorization: Bearer TU_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"numero":"573001234567","tipo":"document","url":"https://ejemplo.com/doc.pdf"}'
Ubicación
curl -X POST https://TU_SERVIDOR/api/whatsapp/enviar \
  -H "Authorization: Bearer TU_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"numero":"573001234567","tipo":"location","latitud":4.7110,"longitud":-74.0086,"nombre":"Bogotá"}'
Respuesta exitosa
{ "success": true, "message": "Mensaje enviado correctamente" }

API Keys

Gestiona las API Keys de tu cuenta. Requieren autenticación con Bearer token (sesión de usuario, no API Key).

GET /api/api-keys Listar keys activas

Devuelve el listado de tus API Keys para que puedas ver cuáles existen, su nombre, si están activas y cuándo se usaron por última vez.
No devuelve el valor completo de la key — ese solo se muestra una vez al crearla. Si la perdiste, debes revocarla y crear una nueva.

Ejemplo
curl -X GET https://TU_SERVIDOR/api/api-keys \
  -H "Authorization: Bearer TU_SESSION_TOKEN"
Respuesta
{ "success": true, "data": [ { "id": 1, "nombre": "Mi App", "key_value": "****************************ab3f", "activo": true, "ultimo_uso": "2026-04-14T10:30:00Z", "created_at": "2026-04-01T09:00:00Z" } ] }
POST /api/api-keys Generar nueva API Key
Body
CampoTipoDescripción
nombre requerido string Nombre descriptivo para identificar la key
Ejemplo
curl -X POST https://TU_SERVIDOR/api/api-keys \
  -H "Authorization: Bearer TU_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"nombre": "Mi App Produccion"}'
Respuesta
{ "success": true, "message": "API Key generada exitosamente", "data": { "id": 2, "nombre": "Mi App Produccion", "key_value": "a3f8e2b1c4d5...", "activo": true, "created_at": "2026-04-15T12:00:00Z" } }
Guarda el valor de key_value inmediatamente. No se volverá a mostrar completo.
DELETE /api/api-keys/:id Revocar una API Key
Ejemplo
curl -X DELETE https://TU_SERVIDOR/api/api-keys/2 \
  -H "Authorization: Bearer TU_SESSION_TOKEN"
Respuesta
{ "success": true, "message": "API Key revocada exitosamente" }

Webhooks

Registra URLs que recibirán notificaciones automáticas cuando ocurran eventos en WhatsApp (mensajes recibidos, cambios de estado, etc.).

GET /api/webhooks Listar webhooks
Ejemplo
curl https://TU_SERVIDOR/api/webhooks \
  -H "Authorization: Bearer TU_API_KEY"
Respuesta
{ "success": true, "data": [ { "id": 1, "url": "https://mi-app.com/webhook", "evento": "mensaje_recibido", "activo": true, "created_at": "2026-04-01T10:00:00Z" } ] }
POST /api/webhooks Crear webhook
Body
CampoTipoDescripción
url requerido string URL HTTPS que recibirá los eventos
evento string Tipo de evento. Default: mensaje_recibido
Ejemplo
curl -X POST https://TU_SERVIDOR/api/webhooks \
  -H "Authorization: Bearer TU_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://mi-app.com/webhook", "evento": "mensaje_recibido"}'
Respuesta
{ "success": true, "message": "Webhook creado exitosamente", "data": { "id": 1, "url": "https://mi-app.com/webhook", "evento": "mensaje_recibido", "activo": true, "secret": "a1b2c3d4e5f6..." } }
Guarda el secret. Lo usarás para validar la firma de los payloads recibidos. Solo se muestra una vez.
PUT /api/webhooks/:id Actualizar webhook
Body (todos opcionales)
CampoTipoDescripción
urlstringNueva URL del webhook
eventostringNuevo tipo de evento
activobooleanActivar o pausar el webhook
Ejemplo — Pausar webhook
curl -X PUT https://TU_SERVIDOR/api/webhooks/1 \
  -H "Authorization: Bearer TU_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"activo": false}'
Respuesta
{ "success": true, "message": "Webhook actualizado exitosamente" }
DELETE /api/webhooks/:id Eliminar webhook
Ejemplo
curl -X DELETE https://TU_SERVIDOR/api/webhooks/1 \
  -H "Authorization: Bearer TU_API_KEY"
Respuesta
{ "success": true, "message": "Webhook eliminado exitosamente" }
PATCH /api/webhooks/:id/regenerate-secret Regenerar secret

Genera un nuevo secret para firmar los payloads. Úsalo si crees que tu secret fue comprometido.

Ejemplo
curl -X PATCH https://TU_SERVIDOR/api/webhooks/1/regenerate-secret \
  -H "Authorization: Bearer TU_API_KEY"
Respuesta
{ "success": true, "message": "Secret regenerado exitosamente", "data": { "id": 1, "secret": "nuevo_secret_aqui..." } }

Payload de Webhook

Cuando ocurre un evento, InnovaAPI hace un POST a tu URL con el siguiente payload y headers.

Headers enviados por InnovaAPI
Content-Type: application/json
X-InnovaAPI-Signature: sha256=abc123...
User-Agent: InnovaAPI/1.0
Valida la firma con tu secret para asegurarte de que el request proviene de InnovaAPI.
La firma se calcula sobre el body JSON tal como llega — usa siempre el raw body, no lo re-serialices.
Payload — Evento: mensaje_recibido
{ "evento": "mensaje_recibido", "timestamp": "2026-04-15T12:00:00Z", "data": { "de": "573001234567@s.whatsapp.net", "nombre": "Juan Pérez", "tipo": "text", "mensaje": "Hola, necesito información", "mensaje_id": "3EB0ABC123" } }
Validar firma en tu servidor
const crypto = require('crypto');
const express = require('express');
const app = express();

// Capturar raw body ANTES de parsear JSON
app.use('/webhook', express.raw({ type: 'application/json' }));

app.post('/webhook', (req, res) => {
  const rawBody = req.body; // Buffer con el JSON crudo
  const sig = req.headers['x-innovaapi-signature'];

  const expected = 'sha256=' + crypto
    .createHmac('sha256', 'TU_SECRET')
    .update(rawBody)
    .digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
    return res.status(401).send('Firma inválida');
  }

  const data = JSON.parse(rawBody);
  // Procesar evento...
  res.sendStatus(200);
});
import hmac, hashlib, json

def validar_firma(body: dict, signature: str, secret: str) -> bool:
    payload = json.dumps(body, separators=(',', ':'))
    expected = 'sha256=' + hmac.new(
        secret.encode(),
        payload.encode(),
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, expected)

# En tu endpoint Flask:
@app.route('/webhook', methods=['POST'])
def webhook():
    sig = request.headers.get('X-InnovaAPI-Signature', '')
    if not validar_firma(request.json, sig, 'TU_SECRET'):
        return 'Firma inválida', 401
    # Procesar evento...
    return '', 200
<?php
function validarFirma(string $body, string $signature, string $secret): bool {
    $expected = 'sha256=' . hash_hmac('sha256', $body, $secret);
    return hash_equals($expected, $signature);
}

$body = file_get_contents('php://input');
$sig  = $_SERVER['HTTP_X_INNOVAAPI_SIGNATURE'] ?? '';

if (!validarFirma($body, $sig, 'TU_SECRET')) {
    http_response_code(401);
    exit('Firma inválida');
}

$data = json_decode($body, true);
// Procesar evento...
http_response_code(200);
?>

Implementación Laravel

Ejemplo completo para integrar InnovaAPI en un proyecto Laravel: recibir webhooks, validar la firma y responder mensajes automáticamente.

1. Variables de entorno (.env)
INNOVAAPI_URL=https://TU_SERVIDOR/api
INNOVAAPI_KEY=tu_api_key_aqui
INNOVAAPI_WEBHOOK_SECRET=tu_secret_del_webhook

# Opcional: si usas Claude AI para responder mensajes
ANTHROPIC_API_KEY=tu_anthropic_key
2. Ruta en routes/api.php
Esta ruta debe estar fuera del middleware auth ya que InnovaAPI la llama directamente. La validación se hace por firma HMAC.
<?php
use App\Http\Controllers\Api\WebhookController;

Route::post('/webhook/whatsapp', [WebhookController::class, 'whatsapp']);

URL resultante: https://tu-app.com/api/webhook/whatsapp

3. Controlador — app/Http/Controllers/Api/WebhookController.php
<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;

class WebhookController extends Controller
{
    private $innovaapiUrl;
    private $innovaapiKey;

    public function __construct()
    {
        $this->innovaapiUrl = env('INNOVAAPI_URL', 'http://localhost:3001/api');
        $this->innovaapiKey = env('INNOVAAPI_KEY');
    }

    // Cambiar a 'ia' para usar Claude AI o 'menu' para respuestas predefinidas
    private $modoRespuesta = 'ia';

    /**
     * Recibir webhook de InnovaAPI
     * POST /api/webhook/whatsapp
     */
    public function whatsapp(Request $request)
    {
        try {
            $signature = $request->header('X-InnovaAPI-Signature');
            if (!$signature) {
                Log::warning('Webhook rechazado: sin firma');
                return response()->json(['ok' => false, 'error' => 'Sin firma'], 401);
            }

            $secret = env('INNOVAAPI_WEBHOOK_SECRET');
            if (!$secret) {
                Log::error('Webhook: secret no configurado en .env');
                return response()->json(['ok' => false, 'error' => 'Configuración incompleta'], 500);
            }

            $payload = $request->getContent();
            $expectedSignature = 'sha256=' . hash_hmac('sha256', $payload, $secret);

            if (!hash_equals($expectedSignature, $signature)) {
                Log::warning('Webhook rechazado: firma inválida');
                return response()->json(['ok' => false, 'error' => 'Firma inválida'], 401);
            }

            $data    = $request->json()->all();
            $numero  = $data['numero']  ?? null;
            $mensaje = $data['mensaje'] ?? null;
            $evento  = $data['evento']  ?? 'mensaje_recibido';

            if (!$numero || !$mensaje) {
                return response()->json(['ok' => false, 'error' => 'Datos incompletos'], 400);
            }

            if ($evento === 'mensaje_recibido') {
                $this->procesarMensajeRecibido($numero, $mensaje);
            }

            return response()->json(['ok' => true]);

        } catch (\Exception $e) {
            Log::error('Error en webhook', ['error' => $e->getMessage()]);
            return response()->json(['ok' => false, 'error' => 'Error procesando'], 500);
        }
    }

    /**
     * Procesar mensaje y generar respuesta
     */
    private function procesarMensajeRecibido($numero, $mensaje)
    {
        $respuesta = $this->modoRespuesta === 'ia'
            ? $this->generarRespuestaIA($mensaje)
            : $this->generarRespuestaMenu($mensaje);

        if ($respuesta) {
            $this->enviarRespuestaViaInnovaAPI($numero, $respuesta);
        }
    }

    /**
     * Generar respuesta usando Claude AI
     * Lee un esquema de BD y puede ejecutar consultas SQL
     */
    private function generarRespuestaIA($mensaje)
    {
        try {
            $apiKey = env('ANTHROPIC_API_KEY');
            if (!$apiKey) {
                return 'Lo siento, el asistente no está disponible en este momento.';
            }

            // Leer el esquema de tu BD (archivo markdown con la estructura)
            $esquema = file_get_contents(base_path('eschema/eschema.md'));

            // Primera llamada: Claude genera SQL si es necesario
            $response = Http::withHeaders([
                'x-api-key'         => $apiKey,
                'anthropic-version' => '2023-06-01',
                'content-type'      => 'application/json',
            ])->post('https://api.anthropic.com/v1/messages', [
                'model'      => 'claude-haiku-4-5-20251001',
                'max_tokens' => 1024,
                'system'     => $esquema,
                'messages'   => [
                    ['role' => 'user', 'content' => $mensaje]
                ]
            ]);

            if (!$response->successful()) {
                return 'Ocurrió un error al procesar tu consulta.';
            }

            $respuestaTexto = $response->json('content.0.text');

            // Si Claude generó SQL, ejecutarlo y devolver resultados
            if (preg_match('/```sql\s*(.*?)\s*```/si', $respuestaTexto, $matches)) {
                $sql = trim($matches[1]);
                Log::info('SQL generado por Claude', ['sql' => $sql]);

                $resultados = DB::select($sql);

                if (empty($resultados)) {
                    return 'No encontré datos para tu consulta.';
                }

                // Segunda llamada: Claude interpreta los resultados en lenguaje natural
                $response2 = Http::withHeaders([
                    'x-api-key'         => $apiKey,
                    'anthropic-version' => '2023-06-01',
                    'content-type'      => 'application/json',
                ])->post('https://api.anthropic.com/v1/messages', [
                    'model'      => 'claude-haiku-4-5-20251001',
                    'max_tokens' => 512,
                    'system'     => $esquema,
                    'messages'   => [
                        ['role' => 'user',      'content' => $mensaje],
                        ['role' => 'assistant', 'content' => $respuestaTexto],
                        ['role' => 'user',      'content' => 'Resultados: ' . json_encode($resultados)],
                    ]
                ]);

                return $response2->json('content.0.text') ?? 'No pude interpretar los resultados.';
            }

            return $respuestaTexto;

        } catch (\Exception $e) {
            Log::error('Error en generarRespuestaIA', ['error' => $e->getMessage()]);
            return 'Ocurrió un error al procesar tu consulta.';
        }
    }

    /**
     * Generar respuesta con menú predefinido
     * Alternativa a la IA — cambia $modoRespuesta = 'menu'
     */
    private function generarRespuestaMenu($mensaje)
    {
        $msg = strtolower(trim($mensaje));

        if (strpos($msg, 'hola') !== false) {
            return "¡Hola! Bienvenido.\n\n¿Qué necesitas?\n1️⃣ Opción A\n2️⃣ Opción B\n3️⃣ Ayuda";
        }
        if ($msg === '1') return 'Respuesta para opción A.';
        if ($msg === '2') return 'Respuesta para opción B.';
        if ($msg === '3') return 'Para ayuda escribe tu pregunta.';

        return 'No entendí tu mensaje. Escribe *hola* para ver el menú.';
    }

    /**
     * Enviar respuesta via InnovaAPI
     */
    private function enviarRespuestaViaInnovaAPI($numero, $respuesta)
    {
        try {
            $response = Http::withHeaders([
                'Authorization' => 'Bearer ' . $this->innovaapiKey
            ])->post($this->innovaapiUrl . '/whatsapp/enviar', [
                'numero'  => $numero,
                'tipo'    => 'text',
                'mensaje' => $respuesta
            ]);

            if (!$response->successful()) {
                Log::error('Error enviando respuesta', [
                    'numero' => $numero,
                    'status' => $response->status()
                ]);
            }

        } catch (\Exception $e) {
            Log::error('Error en enviarRespuestaViaInnovaAPI', ['error' => $e->getMessage()]);
        }
    }
}
4. Esquema de BD para Claude (eschema/eschema.md)

Si usas el modo IA, crea un archivo eschema/eschema.md en la raíz del proyecto describiendo tu base de datos. Claude lo usará como contexto para generar consultas SQL.

# Sistema de gestión

Eres un asistente que responde preguntas sobre la base de datos.
Si necesitas datos, genera una consulta SQL dentro de bloques ```sql```.
Responde siempre en español, de forma breve y clara.

## Tablas disponibles

### tabla_ejemplo
- id (int)
- nombre (varchar)
- fecha (datetime)
- estado (enum: activo, inactivo)
El modo IA solo ejecuta consultas SELECT. Valida en tu esquema que Claude no genere INSERTs o DELETEs si tu BD lo permite.
Flujo completo
Usuario envía mensaje por WhatsApp
        ↓
InnovaAPI recibe el mensaje
        ↓
InnovaAPI hace POST a tu URL con firma HMAC
        ↓
Tu Laravel valida la firma
        ↓
  [modo 'ia']  →  Claude genera SQL → Consulta BD → Claude interpreta → Respuesta
  [modo 'menu'] →  Switch por palabras clave → Respuesta
        ↓
Tu Laravel llama a InnovaAPI /whatsapp/enviar
        ↓
Usuario recibe la respuesta por WhatsApp