Autocosmos WebHooks V1

Introdución

Hace muchísimos años los developers estábamos acostumbrados a ir a buscar la información donde estaba cada tanto tiempo; polling lo llamaban. Por mucho tiempo pareció una cosa normal que otro sistema viniera a buscar información cada tanto hasta que...
Hasta que ese cada tanto entró en la impaciencia del business y el tanto se transformó en minutos o segundos y el sistema que venía a buscar se transformó en una miríada de aplicaciones. Así el polling se trasformó en un sistema ineficiente sea por tiempo que por gasto innecesario de recursos.
Todos sabíamos que algo había que hacer, pero pocos nos movíamos hasta... hasta que aparecieron devices chiquitos con poca potencia y escasa conectividad que pero necesitaban actualizaciones casi en tiempo real: nacieron las push-notifications.

¿Qué es un webhook?

Técnicamente es una "user-defined callbacks made with HTTP" o, aún más simple y ancestral, un webhook es un POST a un endpoint.
En la www se encuentran varias definiciones, la que más calza es:
Polling es como tocar a la puerta de tu amigo y preguntar si tiene azúcar, pero tienes que ir y pedirla cada vez que la quieras. Los webhooks son como alguien que arroja una bolsa de azúcar a tu casa cada vez que la tiene.
Si le dedicas unos segundos a intentar imaginar esa situación, tendrás en tu mente como funciona realmente un webhook.
No importa si estás en casa o no lo estás, alguien te tira la bolsa de azúcar, frente la puerta de tu casa sin tocar tu puerta. Eso es realmente como funcionan los webhooks.
Dicho en una forma más educada, un webhook es una notificación, hacia el mundo externo, de un evento ocurrido en un sistema.
Cada sistema decide como notificar y quien recibe el POST se adapta a traducirlo para sus necesidades. Hay sistemas que ponen toda la información del evento directamente en el content del POST así como hay sistemas que solo envían un ID para que luego se vaya a buscar la información vía API con autenticación.
En Autocosmos los webhooks contienen toda la información disponible y compartible, sobre un evento sin necesidad de acceder, luego, a nuestra API. Cada usuario de Autocosmos tiene disponibles sus webhooks, las cuentas profesionales tienen webhooks de suscripción (independientes de los webhooks personales) y también las apps, que tienen acceso a nuestra API, tienen sus propios webhooks independientes.

Callback

La callback es la URI donde enviar la información o sea donde enviar el POST.
La callback tiene que ser públicamente accesible y no tiene que tener un sistema de login (recuerda siempre la bolsa de azúcar). Hay sistemas que te obligan a registrarte con app habilitada y te permiten ingresar una única URI para cualquier tipo de notificación reciba desde ese sistema. En algunos casos, el hecho de tener un único punto de entrada, puede ser cómodo pero, en muchos casos, es una limitante ya que no permite personalizar la URL, no permite escalar y a veces hasta dificulta la fase de testing o cambio de endpoint.
En Autocosmos cada webhook tiene su propia callback y sus propios headers y para un mismo evento (topic) puedes tener más de un webhook funcionando al mismo tiempo.

Tu callback puede lucir como:

  • https://notifications.midominio.com/autocosmos.
  • https://api.midominio.com/autocosmos/1234 donde 1234 es el ID de un account en tu sistema.
  • https://api.midominio.com/autocosmos?code=AFH1345BG donde AFH1345BG codigo de acceso.
  • Una combinación de todo lo anterior.
  • Cualquier URL sirva a tu sistema.
Obviamente no es necesario que tengas un dominio tuyo para recibir nuestros webhooks; hay varias apps capaces de recibir y trasformar webhooks de Autocosmos (ver "Consumiendo webhooks").

Topics

El topic es básicamente un alfanumérico con el cual Autocosmos identifica un tipo de evento. Ejemplos de valor de topic son:

  • publicacion_consultada
  • catalogo_cotizacion
  • postventa_consultada
  • testdrive_consultado
  • mipromo_cotizacion
  • comprausado_oferta
  • comprausado_disponible
  • ...

Si tu app es una app registrada para el uso de nuestra API, vía API solo tienes acceso a configurar el topic publicacion_consultada. Cada usuario de Autocosmos podrá configurar los demás topics para que tu app reciba más notificaciones.

Headers

Siendo el webhook un request con HTTP-Method POST también se envían una serie de HTTP-Headers. A los headers que te enviaremos podrás, para cada webhook, agregar tu custom headers o sea que, así como viste para la callback también los headers son custom así que los podrás utilizar para agregar códigos, IDs o la info que quieras tener disponible al momento de recibir el POST y procesar la notificación.
Además de los headers standard de HTTP, Autocosmos te envíará unos headers propios algunos de los cuales veremos más adelante (seguridad). Un header que recibirá siempre es Notification-Topic, cuyo valor es uno de los mencionados anteriormente, es decir que no es necesario deserializar el content del request para conocer su formato.

Payloads

Todos los payloads (content de POST) son en JSON.
Si bien cada topic tiene su set de información disponible, para todos los topics relacionados a consultas (a publicación, postvent, testdrive, etc.) hemos tratado de mantener la estructura del JSON prácticamente iguales. De esa forma te será más simple establecer un endpoint único para recibir varios topics de consultas.
Ejemplos específicos están disponibles aquí abajo y en cada cuenta creando un webhook y usando la funcionalidad de Test. Solo como ejemplo mostramos uno de los payloads:

{
  "CountryIso3": "COL",
  "Id": "10a989aef6884e771234f43e9f48339b",
  "Momento": "2021-01-01T00:02:20.123456+00:00",
  "Canonical": "https://www.autocosmos.com.co/catalogo/2022/honda/hr-v/ex/99999999",
  "TipoAnuncio": "catalogo",
  "OrigenLead": "form",
  "LeadState": "update",
  "RefAutocosmos": "00000123456",
  "Vehiculo": {
    "Marca": "Honda",
    "Modelo": "HR-V",
    "Version": "EX",
    "VersionYear": 2021,
    "Kilometraje": 0,
    "Color": "Violeta",
    "BodyType": "SUV",
    "Estado": "nuevo",
    "Precio": {
      "Moneda": "COP",
      "Valor": 104000000.0
    },
    "Anticipo": null,
    "Cuota": null,
    "ExternalId": null
  },
  "Cliente": {
    "Interes": "compra",
    "Nombre": "Angelo",
    "Apellido": "Moriondo",
    "Email": "angelomoriondo@gmail.com",
    "Telefono": "3201234567",
    "Provincia": "Cundinamarca",
    "Ciudad": "Girardot",
    "Comentario": "Me interesa recibir una cotización detallada de Honda HR-V EX.",
    "DetallesOperacion": {
      "EntregaVehiculo": {
        "Marca": "Ford",
        "Modelo": "Escort",
        "VersionYear": 2005
      },
      "FinanciacionDeseada": null,
      "QuiereCotizacionSeguro": true
    }
  },
  "Contacto": {
    "Email": "vendedor@agencia.com",
    "Nombre": "Dino",
    "Apellido": "Ferrari"
  }
}

Te llamamos la atención en dos propiedades: "Id" y "LeadState". El "Id" representa el identificador unívoco del evento o sea que, en este caso, no hay otra consulta, con el mismo "Id". El "LeadState" en este caso es "update" lo que te hace entender que, anteriormente, se envió el mismo evento (o sea el mismo "Id") con "create".
El envío múltiple de un evento con el mismo "Id" puede ocurrir por varios motivos:

  • Te estamos enviando información actualizada de un evento que ya te enviamos.
  • La primera vez que te mandamos el POST tu servidor contestó con un HTTP-Status >= 500.
  • La primera vez que te mandamos el POST tu servidor no contestó rápidamente (no esperamos él response por mucho tiempo).
Dependiendo de como responda tu endpoint es totalmente posible que, en algunos casos, tengas la impresión de recibir un "update" antes de un "create". Es importante que tengas en cuentas estas consideraciones para implementar correctamente el receptor de webhook; si ya tienes varios receptores de webhooks de otras apps ya pasaste por estos temas.

Ejemplo de payload per topic.

{
  "Id": "7df5e9e2934248f4ae6aadc4f47d3ccb",
  "Momento": "2025-01-08T01:27:11.9458024+00:00",
  "TipoAnuncio": "catalogo",
  "OrigenLead": "form",
  "LeadState": "create",
  "RefAutocosmos": "00000012345",
  "Vehiculo": {
    "Marca": "Marca",
    "Modelo": "Modelo",
    "Version": "Versión",
    "VersionYear": 2025,
    "Kilometraje": 0,
    "Color": "",
    "BodyType": "BodyType",
    "Estado": "nuevo",
    "Precio": {
      "Moneda": "USD",
      "Valor": 999999
    },
    "Anticipo": null,
    "Cuota": null,
    "ExternalId": null
  },
  "Cliente": {
    "Interes": "compra",
    "Nombre": "Nombre",
    "Apellido": "Apellido",
    "Email": "prospecto@dominio.com",
    "Telefono": "(0123) 12345678",
    "DocIdentidad": "",
    "Provincia": "Provincia",
    "Ciudad": "Ciudad",
    "Comentario": "Comentarios",
    "DetallesOperacion": {
      "EntregaVehiculo": {
        "Marca": "Marca",
        "Modelo": "Modelo",
        "VersionYear": 2023
      },
      "FinanciacionDeseada": {
        "Anticipo": {
          "Moneda": "USD",
          "Valor": 99999
        },
        "Cuota": {
          "Moneda": "USD",
          "Valor": 99999
        }
      },
      "QuiereCotizacionSeguro": true
    }
  },
  "Contacto": {
    "Email": "vendedor@dominio.com",
    "Nombre": "Nombre",
    "Apellido": "Apellido",
    "Dealer": true
  },
  "CountryIso3": "ECU",
  "Canonical": "http://url_autocosmos.com/algo"
}
{
  "Id": "f7b4ae9e52494716a0de1c24d9a36530",
  "Momento": "2025-01-08T01:27:11.946154+00:00",
  "TipoAnuncio": "none_usadodisponible",
  "OrigenLead": "form",
  "LeadState": "create",
  "RefAutocosmos": "h_00000012345",
  "Vehiculo": {
    "Marca": "Marca",
    "Modelo": "Modelo",
    "Version": "",
    "VersionYear": 2025,
    "Kilometraje": 12345,
    "Color": "",
    "BodyType": "",
    "Estado": "usado",
    "Precio": {
      "Moneda": "USD",
      "Valor": 999999
    }
  },
  "Cliente": {
    "Interes": "venta",
    "Nombre": "Nombre",
    "Apellido": "Apellido",
    "Email": "prospecto@dominio.com",
    "Telefono": "(0123) 12345678",
    "Provincia": "Provincia",
    "Ciudad": "Ciudad",
    "Comentario": ""
  },
  "Contacto": {
    "Email": "comprador@dominio.com",
    "Nombre": "Nombre",
    "Apellido": "Apellido"
  },
  "CountryIso3": "ECU",
  "Canonical": ""
}
{
  "Id": "123_456_789",
  "Momento": "2025-01-08T01:27:11.9462402+00:00",
  "TipoAnuncio": "none_usadooferta",
  "OrigenLead": "form",
  "LeadState": "acepted",
  "RefAutocosmos": "compra_00000012345",
  "Vehiculo": {
    "Marca": "Marca",
    "Modelo": "Modelo",
    "Version": "",
    "VersionYear": 2025,
    "Kilometraje": 12345,
    "Color": "",
    "BodyType": "",
    "Estado": "usado",
    "Precio": {
      "Moneda": "USD",
      "Valor": 999999
    }
  },
  "Cliente": {
    "Interes": "venta",
    "Nombre": "Nombre",
    "Apellido": "Apellido",
    "Email": "prospecto@dominio.com",
    "Telefono": "(0123) 12345678",
    "Provincia": "Provincia",
    "Ciudad": "Ciudad",
    "Comentario": ""
  },
  "Contacto": {
    "Email": "comprador@dominio.com",
    "Nombre": "Nombre",
    "Apellido": "Apellido"
  },
  "CountryIso3": "ECU",
  "Canonical": ""
}
{
  "Id": "1234_b165e9bf98674ee48806d6948add41d7",
  "AdminMail": "franco@superauto.com",
  "NombreComercial": "Super Auto",
  "Provincia": "Modena",
  "Ciudad": "Maranello",
  "Direccion": "Corso Sempreviva 742",
  "Telefono": "+xx 9xx 1234567",
  "AdminNombre": "Franco",
  "AdminApellido": "Ghigliermini",
  "CountryIso3": "ECU",
  "Canonical": null
}
{
  "Id": "46944e9d259c4eeeab6bf6052c9447ad",
  "Momento": "2025-01-08T01:27:11.9460869+00:00",
  "TipoAnuncio": "promo",
  "OrigenLead": "form",
  "LeadState": "create",
  "RefAutocosmos": "algo_algo",
  "Vehiculo": {
    "Marca": "Marca",
    "Modelo": "Modelo",
    "Version": "Versión",
    "VersionYear": 2025,
    "Kilometraje": 0,
    "Color": "",
    "BodyType": "BodyType",
    "Estado": "nuevo",
    "Precio": {
      "Moneda": "USD",
      "Valor": 999999
    },
    "Anticipo": null,
    "Cuota": null,
    "ExternalId": null
  },
  "Cliente": {
    "Interes": "compra",
    "Nombre": "Nombre",
    "Apellido": "Apellido",
    "Email": "prospecto@dominio.com",
    "Telefono": "(0123) 12345678",
    "DocIdentidad": "",
    "Provincia": "Provincia",
    "Ciudad": "Ciudad",
    "Comentario": "Comentarios"
  },
  "Contacto": {
    "Email": "vendedor@dominio.com",
    "Nombre": "Nombre",
    "Apellido": "Apellido",
    "Dealer": true
  },
  "CountryIso3": "ECU",
  "Canonical": "http://url_autocosmos.com/algo"
}
{
  "Id": "ec08b5c58a884c0a856953fc7de51070",
  "Momento": "2025-01-08T01:27:11.9459359+00:00",
  "TipoAnuncio": "postventa",
  "OrigenLead": "form",
  "LeadState": "create",
  "RefAutocosmos": "00000012345",
  "Vehiculo": {
    "Marca": "Marca",
    "Modelo": "Modelo",
    "Version": null,
    "VersionYear": 2023,
    "Kilometraje": 12345,
    "Estado": "usado"
  },
  "Cliente": {
    "Interes": "servicio",
    "Nombre": "Nombre",
    "Apellido": "Apellido",
    "Email": "prospecto@dominio.com",
    "Telefono": "(0123) 12345678",
    "DocIdentidad": "",
    "Provincia": "Provincia",
    "Ciudad": "Ciudad",
    "Comentario": "Comentarios"
  },
  "Contacto": {
    "Email": "responsable@dominio.com",
    "Nombre": "Nombre",
    "Apellido": "Apellido"
  },
  "CountryIso3": "ECU",
  "Canonical": ""
}
{
  "Id": "1418f5d745a14c5fb5c7ef5f2fa07c29",
  "State": "update",
  "Momento": "2025-01-08T01:27:11.9463849+00:00",
  "EstadoActual": "Suspendida",
  "Exposicion": "Media",
  "Marca": "Ferrari",
  "Modelo": "Dino",
  "Version": "246 GTS",
  "VersionYear": 1974,
  "Kilometraje": 123000,
  "Color": "Amarillo",
  "BodyType": "convertible",
  "Precio": {
    "Moneda": "USD",
    "Valor": 500000
  },
  "Anticipo": null,
  "Cuota": null,
  "RefInventario": "x25/4",
  "ExternalId": "El-ID-usado-por-la-app-de-publicacion",
  "Telefono": "+xx 9xx 1234567",
  "Locacion": {
    "Provincia": "Modena",
    "Ciudad": "Maranello",
    "Direccion": "Corso Sempreviva 742"
  },
  "OperadorResponsable": "franco@superauto.com",
  "CountryIso3": "ECU",
  "Canonical": "https://url_autocosmos.com/algo"
}
{
  "Id": "01fd7852ee674230b58f07c4014862b9",
  "Momento": "2025-01-08T01:27:11.9455035+00:00",
  "OrigenLead": "form",
  "LeadState": "create",
  "RefAutocosmos": "797ab6945e994dfabda8d169153382e2",
  "Vehiculo": {
    "RefInventario": "x25/4",
    "Marca": "Marca",
    "Modelo": "Modelo",
    "Version": "Versión",
    "VersionYear": 2025,
    "Kilometraje": 0,
    "Color": "Color",
    "BodyType": "BodyType",
    "Estado": "nuevo",
    "Precio": {
      "Moneda": "USD",
      "Valor": 999999
    },
    "Anticipo": null,
    "Cuota": null,
    "ExternalId": "El-ID-usado-por-la-app-de-publicacion"
  },
  "TipoAnuncio": "publicacion",
  "Cliente": {
    "Interes": "compra",
    "Nombre": "Nombre",
    "Apellido": "Apellido",
    "Email": "prospecto@dominio.com",
    "Telefono": "(0123) 12345678",
    "DocIdentidad": "",
    "Provincia": "Provincia",
    "Ciudad": "Ciudad",
    "Comentario": "Comentarios",
    "DetallesOperacion": {
      "EntregaVehiculo": {
        "Marca": "Marca",
        "Modelo": "Modelo",
        "VersionYear": 2023
      },
      "FinanciacionDeseada": {
        "Anticipo": {
          "Moneda": "USD",
          "Valor": 99999
        },
        "Cuota": {
          "Moneda": "USD",
          "Valor": 99999
        }
      },
      "QuiereCotizacionSeguro": true
    }
  },
  "Contacto": {
    "Email": "vendedor@dominio.com",
    "Nombre": "Nombre",
    "Apellido": "Apellido",
    "Dealer": true
  },
  "CountryIso3": "ECU",
  "Canonical": "https://url_autocosmos.com/algo"
}
{
  "CountryIso3": "ECU",
  "Momento": "2025-01-08T01:27:11.9464238",
  "Id": "1234_b165e9bf98674ee48806d6948add41d7",
  "NombreComercial": "Super Auto",
  "AdminMail": "franco@superautos.com"
}
{
  "Id": "c22adb870cba4133b97b42a70c9a3fd9",
  "Momento": "2025-01-08T01:27:11.9460086+00:00",
  "TipoAnuncio": "catalogo",
  "OrigenLead": "form",
  "LeadState": "create",
  "RefAutocosmos": "00000012345",
  "Vehiculo": {
    "Marca": "Marca",
    "Modelo": "Modelo",
    "Version": null,
    "VersionYear": null,
    "Kilometraje": 0,
    "Color": "",
    "BodyType": null,
    "Estado": "nuevo",
    "Precio": null,
    "Anticipo": null,
    "Cuota": null,
    "ExternalId": null
  },
  "Cliente": {
    "Interes": "testdrive",
    "Nombre": "Nombre",
    "Apellido": "Apellido",
    "Email": "prospecto@dominio.com",
    "Telefono": "(0123) 12345678",
    "DocIdentidad": "",
    "Provincia": "Provincia",
    "Ciudad": "Ciudad",
    "Comentario": "Comentarios",
    "DetallesOperacion": null
  },
  "Contacto": {
    "Email": "vendedor@dominio.com",
    "Nombre": "Nombre",
    "Apellido": "Apellido",
    "Dealer": true
  },
  "CountryIso3": "ECU",
  "Canonical": "http://url_autocosmos.com/algo"
}
{
  "Id": 1234,
  "Nombre": "Versión Nombre",
  "NickName": "version-nombre",
  "Anio": 2025,
  "Marca": {
    "Id": 1234,
    "Nombre": "Marca Nombre",
    "NickName": "marca-nombre"
  },
  "BodyType": {
    "Id": 1234,
    "Nombre": "BodyType Nombre",
    "NickName": "bodytype-nombre"
  },
  "Origen": "Origen",
  "Garantia": "Garantia",
  "Modelo": {
    "Id": 1234,
    "Nombre": "Modelo Nombre",
    "NickName": "modelo-nombre"
  },
  "Precio": {
    "Moneda": "USD",
    "Valor": 999999
  },
  "CountryIso3": "ECU",
  "Canonical": "http://url_version_autocosmos.com/algo"
}
{
  "Id": 1234,
  "Nombre": "Versión Nombre",
  "NickName": "version-nombre",
  "Anio": 2025,
  "Marca": {
    "Id": 1234,
    "Nombre": "Marca Nombre",
    "NickName": "marca-nombre"
  },
  "BodyType": {
    "Id": 1234,
    "Nombre": "BodyType Nombre",
    "NickName": "bodytype-nombre"
  },
  "Origen": "Origen",
  "Garantia": "Garantia",
  "Modelo": {
    "Id": 1234,
    "Nombre": "Modelo Nombre",
    "NickName": "modelo-nombre"
  },
  "Precio": {
    "Moneda": "USD",
    "Valor": 999999
  },
  "CountryIso3": "ECU",
  "Canonical": "http://url_version_autocosmos.com/algo"
}

Seguridad

¿Recuerdas el tipo que "arroja una bolsa de azúcar a tu casa cada vez que la tiene"? Si bien no toca ni tu puerta, ni tu timbre es posible que te deje un mensajito para que sepas si confiar o no en lo que hay adentro la bolsa.
Así como te habrá pasado con webhooks de otras apps, no puedes confiar en la IP de proveniencia (nadie te asegura que sea siempre la misma), ni requerir un determinado "certificado SSL" y menos cookies. Quien enviamos webhooks no lo hacemos de servidores fijos (si es siempre la misma IP puedes considerarla una mera casualidad), ni de un domain específico (si ves un domain de proveniencia del request es otra mera casualidad) y menos guardamos informaciones que nos vienen en los responses.
¿Entonces? ¿Cómo puedes prevenir que algún mal intencionado te envíe cualquier POST a tu endpoint? Una posibilidad muy simple es que tu mismo pongas algún código de auth en la URL de tu callback. Otra posibilidad es que definas algún header custom con algún código de auth. Otra posibilidad es que valide alguna de la información que te envíamos; por ejemplo los emails de los vendedores o, en el caso publicaste por nuestra API, tu "ExternalId".
Ten siempre en cuenta, que estos tipos de comunicaciones son server-to-server y no es tan simple que alguien intercepte un request, de todas formas, para los desconfiados, agregamos una posibilidad más: un hash-based message authentication code (HMAC).
En la configuración de un webhook tienes disponible un Secret que, si lo especificas, será usado para calcular el valor del header Notification-Signature con el HMAC256 calculado con los valores (UTF-8 encoded) los headers Notification-Hook-ID, Notification-Timestamp y Notification-Topic. Con el HMAC en juego ya se hace bastante complicado falsificar un POST especialmente si logras mantener secret tu Secret.

Consumiendo webhooks

Debido a la difusión/proliferación de aplicaciones que proveen webhooks (aka push notifications) nacieron servicios que se ocupan de recibir webhooks e integrarlos en tu circuito de informaciones.
Algunos de estos servicios son: Zapier, IFTTT, LeadsBridge, Automate IO, Microsoft Flow/Power Automate.
Aunque ya existan servicios que resuelven el tema, muchos somos los que terminamos implementado algo custom...

Autocosmos no solo consume webhooks de otras apps sino que los distintos tipo/formatos de comunicación (ADF-API, ADF-mails, etc.) son, en realidad, consumers de nuestros webhooks que transforman el payload en algo distinto; lo mismo pasa con formatos específicos de algunas marcas como BMW, TOYOTA, RENAULT, CITRÖEN, etc.
Para decirlo de otra forma: los developers de Autocosmos fuimos los primeros en implementar consumers de nuestros propios webhooks.
En esta sección te queremos transmitir un poco de nuestra experiencia consumiendo webhooks de otras apps y los nuestros.

Nuestra experiencia

Y empezamos otra vez desde el inicio... ¿Recuerdas el tipo ese que "arroja una bolsa de azúcar a tu casa cada vez que la tiene"?
Quien envíamos webhooks, como acto de cortesía, y ya que cada request implica un response, podemos controlar si el Http-StatusCode está en los 2xx y si no está allí podemos reintentar enviar el mismo idéntico webhook luego de un tiempito; nada más que eso.
Eso significa que:

  • tiene muy poco sentido contestar con un 3xx a un webhook porque la callback la configuraste tú y no se va a entender que tu server nos mande de otro lado.
  • tiene muy poco sentido contestar con un 4xx porque el formato del content y sus valores son definidos por quien envía el webhook. Podría ser que contestes con un 401 o un 403 pero, again, no hay más auth que no sea definida por quien envía el webhook.
Si tu server o el código de la callback tiene algún problema, es posible que tu servicio responda con un 5xx. Es fácil darse cuenta de que, quien envía webhooks, solo termina "arrojando una bolsa de azúcar".
Con esas consideraciones bien en mente se puede empezar a pensar un sistema que consuma webhooks para luego hacer algo en otro sistema.

Dividimos nuestro webhooks consumer en dos sub-sistemas que comparten la misma persistencia:

  1. El sistema que manda, o sea el "¡sí querida!", quien recibe el webhook y lo guarda.
  2. El sistema "¡a trabajar!", quien levanta el webhook y lo procesa de verdad haciendo n-calls a otras x-APIs

¡Sí querida!

Este sistema expone el endpoint de la callback, hace la validación mínima de el origen del POST y guarda la información recibida. El nombre ¡sí querida! se debe a como contesta siempre a quien envió el webhook. En realidad detrás del "guarda la información recibida" está el verdadero trabajo de este sistema.
En una key-value-table, una table de un RDBMS, un no-sql, una serie de files/blobs, o lo que tengan a mano/prefieras hay que generar un registro de la recepción, su estado (falta enviar a nuestro sistema) y la información as-is del payload del webhook (tal vez con el agregado de algunos custom-header).
Una vez guardada la información se anota que hay algo para elaborar y ya estamos en condición de contestar con un 204, o un 2xx, a quien nos envió el webhook. El anota, en nuestros webhooks-consumers, significa que enviamos un mensaje en un sistema de queue FIFO. Cuando el sistema que envía webhooks tiene actualizaciones (como en el caso de los webhooks de Autocosmos y otros), y el sistema ¡a trabajar! no tolera bien los updates, el mensaje que va a la queue lo ocultamos por unos minutos de forma que cuando llegue el momento sea muy probable que ya hayamos recibido la actualización del webhook.
Siempre debido a las posibles actualizaciones de la información del webhook, es importante usar el "ID" del evento (en el caso de los webhooks de Autocosmos el ID del evento está directamente disponible).

¡A trabajar!

Este sistema es quien realmente hace el trabajo pesado de interpretar, traducir y registrar el evento en nuestro sistema principal (nuestro CRM).
Básicamente se trata de un consumer de lo que dejó anotado el ¡sí querida!. Este sub-sistema es quien realmente tiene/genera problemas, ya que, en el proceso de traducción/procesamiento del webhook, puede terminar chocando con falta de información en el CRM, falta de configuración, constraints definidos en el CRM, etc. Lo que pasará es que un webhook recibido no fue posible registrarlo en el CRM.
Por cada registro de la recepción, nuestro ¡a trabajar! guarda el resultado de la elaboración y, en el caso no se logre ultimar el envío al CRM, también guarda el "motivo del rechazo".

Con ¡sí querida! y ¡a trabajar! operando bajo el mismo techo nos aseguramos de recibir todos los webhooks, procesar cada uno, tener un registro de lo que no se pudo procesar, cambiar reglas/configuraciones/información, volver a procesar los webhooks sin necesidad de pedir reenvíos.

La alternativa lightweight

Supongamos que tu CRM ya tiene implementado algo tipo nuestros ¡sí querida! y ¡a trabajar! para recibir webhooks desde Facebook.
¿Es realmente necesario hacer lo mismo para recibir los webhooks de Autocosmos?
Bueno...como te imaginarás la respuesta real es "depende", pero ese "depende" incluye una de las soluciones que hemos adoptado cuando nos fue posible:
un simple JSON transformer
Al final las informaciones de los leads son casi siempre las mismas así que recibimos el JSON de Autocosmos, lo trasformamos en otro JSON y lo enviamos directo al sistema que ya teníamos funcionando para Facebook.

Esperamos que esta información te sea de utilidad para implementar tus webhooks consumers.
Como siempre, si quieres discutir soluciones y/o compartir tu experiencia sabes que estamos en developers@autocosmos.com.