Ejemplos de Webhooks
Implementaciones completas para diferentes casos de uso.
Sincronizar con tu CRM
Actualiza tu CRM cuando se registra un cliente o se completa una reserva.
Node.js
const express = require('express');
const crypto = require('crypto');
const axios = require('axios');
const app = express();
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;
const CRM_API_KEY = process.env.CRM_API_KEY;
// Middleware para verificar firma
function verifySignature(req, res, next) {
const signature = req.headers['x-salonbookit-signature'];
const payload = req.body.toString();
const expectedHash = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(payload)
.digest('hex');
if (!crypto.timingSafeEqual(
Buffer.from(signature.replace('sha256=', ''), 'hex'),
Buffer.from(expectedHash, 'hex')
)) {
return res.status(401).send('Invalid signature');
}
req.webhookEvent = JSON.parse(payload);
next();
}
app.post('/webhooks/salonbookit',
express.raw({ type: 'application/json' }),
verifySignature,
async (req, res) => {
const event = req.webhookEvent;
try {
switch (event.type) {
case 'customer.created':
await syncNewCustomerToCRM(event.data.customer);
break;
case 'booking.completed':
await updateCRMWithBooking(event.data.booking);
break;
default:
console.log(`Evento no manejado: ${event.type}`);
}
res.json({ received: true });
} catch (error) {
console.error('Error procesando webhook:', error);
res.status(500).json({ error: error.message });
}
}
);
async function syncNewCustomerToCRM(customer) {
await axios.post('https://api.mycrm.com/contacts', {
name: customer.nombre,
email: customer.email,
phone: customer.telefono,
source: 'SalonBookIt',
external_id: `sbk_${customer.id}`
}, {
headers: { 'Authorization': `Bearer ${CRM_API_KEY}` }
});
console.log(`Cliente ${customer.id} sincronizado con CRM`);
}
async function updateCRMWithBooking(booking) {
await axios.post('https://api.mycrm.com/activities', {
contact_external_id: `sbk_${booking.cliente.id}`,
type: 'appointment_completed',
description: `Reserva completada: ${booking.servicios.map(s => s.nombre).join(', ')}`,
value: booking.total,
date: booking.fecha
}, {
headers: { 'Authorization': `Bearer ${CRM_API_KEY}` }
});
}
app.listen(3000);
Notificaciones en Slack
Envia una notificacion a Slack cuando se crea una nueva reserva.
Python
import os
import hmac
import hashlib
import requests
from flask import Flask, request, abort
app = Flask(__name__)
WEBHOOK_SECRET = os.environ['WEBHOOK_SECRET']
SLACK_WEBHOOK_URL = os.environ['SLACK_WEBHOOK_URL']
def verify_signature(payload, signature, secret):
if not signature.startswith('sha256='):
return False
sig_hash = signature[7:]
expected = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()
return hmac.compare_digest(sig_hash, expected)
def send_slack_notification(booking):
"""Envia notificacion de nueva reserva a Slack."""
services = ', '.join([s['nombre'] for s in booking['servicios']])
message = {
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": "Nueva reserva!"
}
},
{
"type": "section",
"fields": [
{"type": "mrkdwn", "text": f"*Cliente:*\n{booking['cliente']['nombre']}"},
{"type": "mrkdwn", "text": f"*Servicios:*\n{services}"},
{"type": "mrkdwn", "text": f"*Fecha:*\n{booking['fecha']}"},
{"type": "mrkdwn", "text": f"*Hora:*\n{booking['hora_inicio']}"},
{"type": "mrkdwn", "text": f"*Profesional:*\n{booking['profesional']['nombre']}"},
{"type": "mrkdwn", "text": f"*Total:*\n{booking['total']}€"}
]
}
]
}
requests.post(SLACK_WEBHOOK_URL, json=message)
@app.route('/webhook', methods=['POST'])
def webhook():
signature = request.headers.get('X-SalonBookIt-Signature', '')
payload = request.get_data()
if not verify_signature(payload, signature, WEBHOOK_SECRET):
abort(401)
event = request.get_json()
if event['type'] == 'booking.created':
send_slack_notification(event['data']['booking'])
return {'received': True}
if __name__ == '__main__':
app.run(port=3000)
Sincronizar con Google Calendar
Crea eventos en Google Calendar cuando se crean reservas.
Node.js
const { google } = require('googleapis');
const express = require('express');
const crypto = require('crypto');
const app = express();
// Configurar cliente de Google Calendar
const oauth2Client = new google.auth.OAuth2(
process.env.GOOGLE_CLIENT_ID,
process.env.GOOGLE_CLIENT_SECRET,
process.env.GOOGLE_REDIRECT_URI
);
oauth2Client.setCredentials({
refresh_token: process.env.GOOGLE_REFRESH_TOKEN
});
const calendar = google.calendar({ version: 'v3', auth: oauth2Client });
async function createCalendarEvent(booking) {
const services = booking.servicios.map(s => s.nombre).join(', ');
const event = {
summary: `Reserva: ${booking.cliente.nombre}`,
description: `Servicios: ${services}\nCliente: ${booking.cliente.nombre}\nTelefono: ${booking.cliente.telefono}`,
start: {
dateTime: `${booking.fecha}T${booking.hora_inicio}:00`,
timeZone: 'Europe/Madrid'
},
end: {
dateTime: `${booking.fecha}T${booking.hora_fin}:00`,
timeZone: 'Europe/Madrid'
},
reminders: {
useDefault: false,
overrides: [
{ method: 'popup', minutes: 30 }
]
}
};
const result = await calendar.events.insert({
calendarId: process.env.CALENDAR_ID,
resource: event
});
console.log('Evento creado:', result.data.htmlLink);
return result.data.id;
}
async function deleteCalendarEvent(bookingId) {
// Buscar evento por ID extendido o descripcion
const events = await calendar.events.list({
calendarId: process.env.CALENDAR_ID,
q: `booking_${bookingId}`
});
if (events.data.items.length > 0) {
await calendar.events.delete({
calendarId: process.env.CALENDAR_ID,
eventId: events.data.items[0].id
});
console.log('Evento eliminado');
}
}
app.post('/webhook',
express.raw({ type: 'application/json' }),
async (req, res) => {
// Verificar firma (ver ejemplo anterior)
const event = JSON.parse(req.body.toString());
switch (event.type) {
case 'booking.created':
await createCalendarEvent(event.data.booking);
break;
case 'booking.cancelled':
await deleteCalendarEvent(event.data.booking.id);
break;
}
res.json({ received: true });
}
);
app.listen(3000);
Generar facturas automaticamente
Genera una factura en tu sistema cuando se completa un pago.
PHP (Laravel)
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Services\InvoiceService;
class WebhookController extends Controller
{
protected InvoiceService $invoiceService;
public function __construct(InvoiceService $invoiceService)
{
$this->invoiceService = $invoiceService;
}
public function handle(Request $request)
{
// Verificar firma
$signature = $request->header('X-SalonBookIt-Signature');
$payload = $request->getContent();
if (!$this->verifySignature($payload, $signature)) {
return response('Invalid signature', 401);
}
$event = json_decode($payload, true);
match ($event['type']) {
'payment.completed' => $this->handlePaymentCompleted($event['data']),
'payment.refunded' => $this->handlePaymentRefunded($event['data']),
default => null
};
return response()->json(['received' => true]);
}
protected function handlePaymentCompleted(array $data): void
{
$payment = $data['payment'];
$booking = $data['booking'];
$customer = $data['cliente'];
// Generar factura
$invoice = $this->invoiceService->create([
'customer_name' => $customer['nombre'],
'customer_email' => $customer['email'],
'items' => collect($booking['servicios'])->map(fn($s) => [
'description' => $s['nombre'],
'quantity' => 1,
'unit_price' => $s['precio'],
])->toArray(),
'total' => $payment['amount'],
'payment_method' => 'Tarjeta (Stripe)',
'reference' => "SBK-{$booking['codigo']}"
]);
// Enviar por email
$this->invoiceService->sendByEmail($invoice, $customer['email']);
logger()->info("Factura generada: {$invoice->number}");
}
protected function handlePaymentRefunded(array $data): void
{
// Generar nota de credito
$this->invoiceService->createCreditNote([
'original_invoice' => "SBK-{$data['booking']['codigo']}",
'amount' => $data['refund']['amount'],
'reason' => $data['refund']['reason']
]);
}
protected function verifySignature(string $payload, ?string $signature): bool
{
if (!$signature || !str_starts_with($signature, 'sha256=')) {
return false;
}
$expected = hash_hmac('sha256', $payload, config('services.salonbookit.webhook_secret'));
return hash_equals($expected, substr($signature, 7));
}
}
Probar webhooks localmente
Usa herramientas como ngrok para exponer tu servidor local:
Terminal
# Instalar ngrok
npm install -g ngrok
# Exponer tu servidor local
ngrok http 3000
# Copiar la URL HTTPS que te da ngrok
# Ejemplo: https://abc123.ngrok.io
# Configurar esa URL en el Dashboard como endpoint del webhook
Tambien puedes usar el boton "Probar" en el Dashboard para enviar un webhook de prueba a tu endpoint.