Tabla de contenidos

Eventos de Lighthouse Feed (Webhooks)

Estás aquí:
< Todos los temas

Establecer la URL de escucha

Para establecer la URL donde escucharás los eventos, ve a Configuración de CMS. Cuando tengas seleccionado Desarrollo a medida, verás un panel que te permite establecer la URL (Es muy recomendable que ésta sea https).
Cuando hayas guardado la URL, podrás probarla con el botón Enviar evento de prueba. Tu Webhook recibirá un evento de tipo «Test» por POST, y deberás responder con un código Http de éxito. Como en este caso el evento «Test» no admite cuerpo de respuesta, puedes devolver simplemente un código 204 (No Content).

Esquema general de los Webhooks

Recibiras una llamada POST a la URL que nos indiques. En el cuerpo de la petición, con el mimeType application/json recibirás un objeto como el que se describe a continuación. Observa que su atributo Value cambiará de tipo en función del evento que estés recogiendo, cosa que tendrás que tener en cuenta a la hora de deserializar el objeto. Para facilitar la deserialización del tipo de objeto correcto, también te enviaremos por la query de la petición un parámetro eventType con el nombre del tipo de evento (Por ejemplo «Test», o «PriceChanges»). De esta manera, podrás deserializar a un WebhookEvent<string>, un WebhookEvent<PriceChangeEvent> u otro tipo, según corresponda. Debajo te mostramos un ejemplo de formato de los eventos de Webhook que enviamos.

WebHookEvent 
{
    "ID": string,
    "EventType": string,
    "UtcDateTime": DateTimeOffset (ISO),
    "Value": string | PriceChangesEvent | FillOrderEvent | [...]
}

PriceChangesEvent
{
   "PriceChanges": PriceChangeEvent[]
}

PriceChangeEvent
{
   "ProductSKU": string,
   "ProductGTIN": string,
   "NewPrice": decimal
}

Secretos de Webhook y autenticación

Aunque para realizar una prueba de conectividad sencilla y comprobar la respuesta en tu tecnología de backend preferida basta con exponer una URL pública que admita POST, en un entorno real es imprescindible que verifiques que los eventos que estás recibiendo verdaderamente proceden de Lighthouse Feed. Para ello:
Una vez que guardes la URL de escucha en Lighthouse Feed, se te ofrecerá la opción de ver el Secreto de tu Webhook:

Usaremos éste Secreto de Webhook para firmar las peticiones que recibas y verificar que realmente han sido generadas por Lighthouse Feed. Para esto, cuando recibas una petición a tu URL recibirás una cabecera Http LighthouseFeed-Signature como ésta:

LighthouseFeed-Signature:t=637915948647279853,s=31-F5-8A-A1-0A-CA-5F-A2-11-D0-12-A0-29-87-6C-8F-F6-E0-F2-52-2F-1D-F2-1D-A2-19-5A-5A-C8-60-E5-49

Deberás leer esta cabecera, trocearla para obtener los valores s y t y comparar el valor s (Signature, firma) con la firma que generarás para el mensaje partiendo del Secreto de Webhook. Ambas deberían ser idénticas. Para generar la firma:

  • Lee el cuerpo de la petición (Es decir, el JSON con el objeto WebhookEvent descrito arriba) como una cadena de texto. No deserialices o formatees esa cadena.
  • Concatena el valor t con el caracter ‘.’ y con el cuerpo de la petición como cadena que acabas de leer. Tu concatenación C Tendrá un aspecto como éste:
637915948647279853.{"ID": "1242553f-8bb2-45fd-8fde-727efc5aabb0","EventType": "Test","UtcDateTime": "2022-06-22T17:28:07.0333992+00:00","Value": "Test event"} 
  • Usa la función criptográfica HMAC-Sha256 (Disponible en cualquier lenguaje de programación mayoritario, ya sea como parte del núcleo del lenguaje o como paquete de terceros), y usando como clave de encriptación tu Secreto de Webhook, calcula el Hash de la concatenación C. Obtendrás un array de bytes, que deberás representar como una cadena Hexadecimal. Si el evento procede verdaderamente de Lighthouse Feed, dicha cadena coincidirá con el valor s que te enviamos.
  • Opcionalmente y para mayor seguridad, el valor t representa la fecha de emisión del evento (Concretamente, el número de Ticks transcurridos en GMT+0 desde el año 1 del calendario gregoriano). Si lo deseas, puedes comparar esa fecha de emisión del evento con la fecha actual. Si es excesivamente anterior (Por ejemplo, más de 1 hora), puedes considerar que el evento puede haber sido manipulado por un tercero.

A continuación presentamos un ejemplo completo, escrito en C# (ASP.Net Core 6), de recepción de la cabecera LighthouseFeed-Signature.

using System.Security.Cryptography;
using System.Text;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;

namespace WebApplication.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class SampleController : ControllerBase
    {
 
        [HttpPost]
        public async Task<IActionResult> Webhook(string eventType)
        {
            if (eventType == "Test")
            {
                WebhookEvent<string>? ev = await GetEvent<string>();
                if (ev != null)
                {
                    //procesa el evento en segundo plano (recomendado)

                    //
                    return NoContent();
                }
                return BadRequest();
            }
            else if (eventType == "PriceChanges")
            {
                WebhookEvent<PriceChangesEvent>? ev = await GetEvent<PriceChangesEvent>();
                if (ev != null)
                {
                    //procesa el evento en segundo plano (recomendado)

                    //
                    //return NoContent();

                    //Alternativamente, puedes devolver una lista de objectos PriceChangeEventResponse
                    //Indicando cuales se han podido procesar y cuales no.

                    return Ok(ev.Value?.PriceChanges.Select(e => new PriceChangeEventResponse
                    {
                        ErrorMessage = "Este mensaje será mostrado en Lighthouse Feed.",
                        Success = false,
                        ProductSKU = e.ProductSKU
                    }));
                }
                return BadRequest();
            }
            return BadRequest();
        }


        private async Task<WebhookEvent<T>?> GetEvent<T>(TimeSpan? timeout = null)
        {
            using StreamReader streamReader = new StreamReader(Request.Body);
            string body = await streamReader.ReadToEndAsync();
            Request.Headers.TryGetValue("LighthouseFeed-Signature", out StringValues signature);
            IDictionary<string, string> headerValues = signature.ToString().Split(",").Select(e =>
            {
                string[] split = e.Split("=");
                return new { Key = split[0], Value = split[1] };
            }).ToDictionary(e => e.Key, e => e.Value);
            using HMACSHA256 hmac = new HMACSHA256(Encoding.UTF8.GetBytes("ba4d55c86c354ed6bae497a81ef5595e")); //Your Webhook Secret
            if (headerValues.ContainsKey("t") && headerValues.ContainsKey("s"))
            {
                using MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(headerValues["t"] + "." + body));
                string actualSignature = BitConverter.ToString(hmac.ComputeHash(ms));
                if (actualSignature == headerValues["s"] && (timeout == null || DateTime.UtcNow <= new DateTime(long.Parse(headerValues["t"]))))
                    return System.Text.Json.JsonSerializer.Deserialize<WebhookEvent<T>>(body);
            }
            return null;
        }


        public class WebhookEvent<T>
        {

            public string? ID { get; set; }
            public string EventType { get; set; }

            public DateTimeOffset UtcDateTime { get; set; }

            public T? Value { get; set; }
        }

        public class PriceChangesEvent
        {
            public List<PriceChangeEvent> PriceChanges { get; set; }

            public class PriceChangeEvent
            {

                public string ProductSKU { get; set; }

                public string ProductGTIN { get; set; }

                public decimal NewPrice { get; set; }

            }
        }

        public class PriceChangeEventResponse
        {
            public string ProductSKU { get; set; }

            public bool Success { get; set; }

            public string ErrorMessage { get; set; }

        }

    }
}

Referencia de Webhooks

Evento Test: Se dispara cuando pulsas la opción «Enviar evento de prueba» en Lighthouse Feed. En el body de tu petición, recibirás un modelo StringWebhookEvent. Debes responder con un estado Http 204 – No Content
Evento PriceChanges: Se dispara cuando un usuario cambia el precio de un producto en tu tienda en Lighthouse Feed. En el body de tu petición, recibirás un modelo PriceChangesEventWebhookEvent. Puedes responder con un estado Http 204 – No Content indicando que aceptas todos los cambios, o con un estado Http 200 -Ok cuyo cuerpo contenga un array de objetos PriceChangeEventResponse, indicando para cada producto si se ha aceptado el cambio o no.

Consejos

Lo ideal es que tu Webhook lea los eventos y no los tramite directamente, sino que los gestione en segundo plano, y devuelva una respuesta a Lighthouse Feed inmediatamente. Esto es porque Lighthouse Feed intentará reenviar eventos para los que no ha recibido respuesta pasado un cierto tiempo (Para protegerte ante caídas puntuales de tu servidor), y si en su lugar decides tramitar el evento en cuanto llega la petición http, podrías recibir mensajes duplicados.

En general, responder a cualquier evento con un estado Http 204 (No Content) significa que has recibido el mensaje y lo vas a aceptar en su totalidad. Hay situaciones en las que, por tus reglas de negocio, podrías querer rechazar la operación. Por ejemplo, en un cambio de precios, podrías desear aceptar únicamente algunos precios. En este caso, te ofrecemos dos soluciones posibles:

  • Acepta el evento inmediatamente con un estado Http 204. Lighthouse Feed asumirá que todos los precios han sido cambiados. Posteriormente y cuando proceses los cambios de precio, comunícanos mediante API o mediante tu Feed los precios que no se han cambiado.
  • En lugar de devolver un estado Http 204, devuelve un estado Http 200 y un objeto de respuesta que indique rechazo de la solicitud para cada uno de los cambios de precios (Observa el ejemplo completo en C# que te hemos mostrado, concretamente el uso de la clase PriceChangeEventResponse). Envíanos un PriceChangeEventResponse con un ErrorMessage en que se indique que la petición se ha rechazado porque se cambiarán los precios a posteriori, y nosotros lo mostraremos al usuario.
Anterior Desarrollos propios