Dashboard

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.