Bienvenido a la documentación de InnovaAPI. Desde aquí podrás enviar mensajes de WhatsApp, gestionar tus API Keys y configurar Webhooks.
Todas las solicitudes requieren tu API Key en el header Authorization:
Authorization: Bearer TU_API_KEY
https://TU_SERVIDOR
Todas las respuestas son JSON con la estructura:
| Código | Significado |
|---|---|
| 401 | API Key inválida o no enviada |
| 400 | WhatsApp no conectado o parámetros incorrectos |
| 404 | Recurso no encontrado |
| 429 | Límite de mensajes diarios alcanzado |
| 500 | Error interno del servidor |
Envía mensajes de WhatsApp: texto, imagen, documento, video, audio o ubicación.
| Campo | Tipo | Descripció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 |
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' }
body: JSON.stringify({
numero: '573001234567',
tipo: 'image',
url: 'https://ejemplo.com/imagen.jpg',
caption: 'Mira esta imagen'
})
body: JSON.stringify({
numero: '573001234567',
tipo: 'location',
latitud: 4.7110,
longitud: -74.0086,
nombre: 'Bogotá, Colombia'
})
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())
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'
}
)
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'
}
)
<?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);
?>
CURLOPT_POSTFIELDS => json_encode([
'numero' => '573001234567',
'tipo' => 'image',
'url' => 'https://ejemplo.com/imagen.jpg',
'caption' => 'Mira esta 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":"text","mensaje":"Hola mundo"}'
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"}'
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"}'
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á"}'
Gestiona las API Keys de tu cuenta. Requieren autenticación con Bearer token (sesión de usuario, no API Key).
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.
curl -X GET https://TU_SERVIDOR/api/api-keys \ -H "Authorization: Bearer TU_SESSION_TOKEN"
| Campo | Tipo | Descripción |
|---|---|---|
nombre requerido |
string | Nombre descriptivo para identificar la key |
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"}'
key_value inmediatamente. No se volverá a mostrar completo.
curl -X DELETE https://TU_SERVIDOR/api/api-keys/2 \ -H "Authorization: Bearer TU_SESSION_TOKEN"
Registra URLs que recibirán notificaciones automáticas cuando ocurran eventos en WhatsApp (mensajes recibidos, cambios de estado, etc.).
curl https://TU_SERVIDOR/api/webhooks \ -H "Authorization: Bearer TU_API_KEY"
| Campo | Tipo | Descripción |
|---|---|---|
url requerido |
string | URL HTTPS que recibirá los eventos |
evento |
string | Tipo de evento. Default: mensaje_recibido |
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"}'
secret. Lo usarás para validar la firma de los payloads recibidos. Solo se muestra una vez.
| Campo | Tipo | Descripción |
|---|---|---|
url | string | Nueva URL del webhook |
evento | string | Nuevo tipo de evento |
activo | boolean | Activar o pausar el webhook |
curl -X PUT https://TU_SERVIDOR/api/webhooks/1 \
-H "Authorization: Bearer TU_API_KEY" \
-H "Content-Type: application/json" \
-d '{"activo": false}'
curl -X DELETE https://TU_SERVIDOR/api/webhooks/1 \ -H "Authorization: Bearer TU_API_KEY"
Genera un nuevo secret para firmar los payloads. Úsalo si crees que tu secret fue comprometido.
curl -X PATCH https://TU_SERVIDOR/api/webhooks/1/regenerate-secret \ -H "Authorization: Bearer TU_API_KEY"
Cuando ocurre un evento, InnovaAPI hace un POST a tu URL con el siguiente payload y headers.
Content-Type: application/json X-InnovaAPI-Signature: sha256=abc123... User-Agent: InnovaAPI/1.0
secret para asegurarte de que el request proviene de InnovaAPI.mensaje_recibido
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);
?>
Ejemplo completo para integrar InnovaAPI en un proyecto Laravel: recibir webhooks, validar la firma y responder mensajes automáticamente.
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
<?php
use App\Http\Controllers\Api\WebhookController;
Route::post('/webhook/whatsapp', [WebhookController::class, 'whatsapp']);
URL resultante: https://tu-app.com/api/webhook/whatsapp
<?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()]);
}
}
}
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)
SELECT. Valida en tu esquema que Claude no genere INSERTs o DELETEs si tu BD lo permite.
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