Webhooks
¿Qué son los webhooks?
Los webhooks permiten suscribirte a ciertos eventos que occuren a nivel de recurso en la api de a3innuva Contabilidad. Por ejemplo, puedes suscribirte a un webhook de cuentas contables para que cuando desde la aplicación o vía API se dé de alta una nueva cuenta, se envíe una petición POST a tu servidor que te avisará que se ha producido un evento de alta.
La mayoría de los recursos de la API permiten suscribirse a notificaciones de cambio, siendo principalmente Updated, Created, Deleted y Disabled.
El evento ‘Disabled’ se utiliza en algunos recursos que no se eliminan, como las cuentas, clientes o proveedores, donde realmente la entidad solo se puede desactivar. El objetivo de estas notificaciones es doble, por un lado, conocer cuando ha finalizado una acción de escritura solicitada a la API, como por ejemplo la creación de una factura, y por otro lado atender a cualquier cambio sobre un recurso determinado, como por ejemplo conocer los cambios realizados sobre los clientes o proveedores.
Recursos que permiten configurar webhooks
Te detallamos la relación de recursos que permiten suscripción:
Eventos a los que puedes suscribirte
Para consultar la lista de eventos, cada webhook dispone del método events
que lanzando una petición GET te devolverá la lista. Por ejemplo, para cuentas contables:
- Http
- Curl
- C#
GET /a3innuva-contabilidad/api/accounts/webhooks/events HTTP/1.1
Host: a3api.wolterskluwer.es
context: none
Ocp-Apim-Subscription-Key: 2kxwf5rb7ffb65dd5
Authorization: Bearer eyIkaWQiOiIxIiwidHlwIjoiSld...
Content-Type: application/json
curl --location 'https://a3api.wolterskluwer.es/a3innuva-contabilidad/api/accounts/webhooks/events' \
--header 'context: none' \
--header 'Ocp-Apim-Subscription-Key: 2kxwf5rb7ffb65dd5' \
--header 'Authorization: Bearer eyIkaWQiOiIxIiwidHlwIjoiSld...' \
--header 'Content-Type: application/json'
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://a3api.wolterskluwer.es/a3innuva-contabilidad/api/accounts/webhooks/events");
request.Headers.Add("context", "none");
request.Headers.Add("Ocp-Apim-Subscription-Key", "2kxwf5rb7ffb65dd5");
request.Headers.Add("Authorization", "Bearer eyIkaWQiOiIxIiwidHlwIjoiSld...");
var content = new StringContent(string.Empty);
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
request.Content = content;
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
Console.WriteLine(await response.Content.ReadAsStringAsync());
[
"a3Innuva.ACC.Accounts.AccountCreated",
"a3Innuva.ACC.Accounts.AccountUpdated",
"a3Innuva.ACC.Accounts.AccountDisabled",
"a3Innuva.ACC.Accounts.ErrorOccurred"
]
Configurando tu servidor
Antes de poder suscribirte a un webhook, es necesario que configures tu servidor para recibir las peticiones HTTP POST que se van a realizar. Algunos aspectos básicos que esperamos de tu endpoint son:
- Debe utilizar HTTPS sobre el puerto estándar 443
- Tiene que responder a la petición HTTP POST antes de 5 segundos con una respuesta HTTP 2xx.
- Los headers de respuesta no deben contener cookies.
- Si la firma es inválida, deberás devolver un error HTTP 401.
Suscribirme a un webhook
Para crear un nuevo webhook deberás enviar una petición POST al método subscribe
del recurso al que quieres suscribirte. Además, deberás informar la lista de eventos que se monitorizarán.
POST /a3innuva-contabilidad/api/accounts/webhooks/subscribe HTTP/1.1
Host: a3api.wolterskluwer.es
context: none
Ocp-Apim-Subscription-Key: 2kxwf5rb7ffb65dd5
Authorization: Bearer eyIkaWQiOiIxIiwidHlwIjoiSld...
Content-Type: application/json
Content-Length: 376
{
"thirdPartyApplicationName": "docuApp",
"url": "https://tudominio.com/endpointwebhooks",
"secret": "d380ca79-feeb-11ed-b90a-7626c28a4e4e",
"eventTypes": [
"a3Innuva.ACC.Accounts.AccountCreated",
"a3Innuva.ACC.Accounts.AccountUpdated",
"a3Innuva.ACC.Accounts.AccountDisabled",
"a3Innuva.ACC.Accounts.ErrorOccurred"
]
}
thirdPartyApplicationName
: es el nombre de tu aplicaciónurl
: es la url de tu servidor que recibirá la petición POST por parte del servidor de Webhooks de WK. Deberá responder con un código HTTP.200 si es correcto.secret
: es una palabra secreta que te servirá para validar posteriormente los payloads que recibas.eventTypes
: array con la relación de eventos a los que nos queremos suscribir para el recurso al que le estamos haciendo la petición (en este caso accounts).
Body.response de la petición POST de suscripción:
{
"responseState": 3,
"webhookId": "c0d18eeb-19ba-43e5-a96a-c4c9898b1e0c",
"errors": []
}
El payload que recibirás en tu servidor será del tipo Acknowledge que indica que se está estableciendo un contrato entre el servidor de webhooks de WK y tu servidor.
{
"WebhookId": "c0d18eeb-19ba-43e5-a96a-c4c9898b1e0c",
"OutgoingId": "f8c74d4c-0d68-4423-8e23-422ca441c016",
"ConversationId": "97c59b09-c15e-4578-b0fe-9a3f938dcfb7",
"TenantId": "2P3N0",
"ExtraPartitionInformation": {},
"EventType": {
"AppName": "a3Innuva",
"Domain": "ACC",
"Resource": "Accounts",
"EventType": "Acknowledge",
"SemanticName": "a3Innuva.ACC.Accounts.Acknowledge"
},
"Timestamp": 638212024101124979,
"JsonData": "{\"Arguments\":\"Acknowledge\"}",
"UserIdentifier": "69ad68a1-1150-4697-9208-aec4008d2c98"
}
Tu servidor deberá responder con un HTTP.200 confirmando el contrato y a partir de este momento, para cualquier evento ocurrido en el recurso de cuentas, se enviará una petición POST a tu servidor:
{
"WebhookId": "c0d18eeb-19ba-43e5-a96a-c4c9898b1e0c",
"OutgoingId": "a31e9d80-ce32-4337-9e4e-f0b6a20a6a79",
"ConversationId": "2b43a4ab-27b8-47ca-8792-e97b60de44cd",
"TenantId": "2P3N0",
"ExtraPartitionInformation": {
"PartitionClientId": "A3TFR:061602:CM:98d52a37-3659-4d01-a526-750888feb5d9",
"UserName": "a3developers@wolterskluwer.com"
},
"EventType": {
"AppName": "a3Innuva",
"Domain": "ACC",
"Resource": "Accounts",
"EventType": "AccountUpdated",
"SemanticName": "a3Innuva.ACC.Accounts.AccountUpdated"
},
"Timestamp": 638212029148599593,
"JsonData": "{\"CorrelationId\":\"9ac0a9aa-a747-4ec3-9065-ef9a51616c3a\",\"Version\":\"06/01/2023 07:55:14\",\"SequenceVersion\":\"638212029146539385\",\"a3InnuvaSourceInterface\":\"ACC.API.COMMAND\",\"a3InnuvaProcessId\":\"5ee75b28-6fdc-432b-b991-c9a10228e037\",\"a3InnuvaSourceAction\":\"ACC\"}",
"UserIdentifier": "69ad68a1-1150-4697-9208-aec4008d2c98"
}
En el payload del ejemplo anterior, puedes ver que el evento que se ha producido es del tipo AccountUpdated
para la empresa A3TFR:061602:CM:98d52a37-3659-4d01-a526-750888feb5d9
y el correlationId
de la cuenta afectada en el cambio es 9ac0a9aa-a747-4ec3-9065-ef9a51616c3a
. Para consultar el cambio, deberemos hacer un GET de accounts:
GET /a3innuva-contabilidad/api/accounts/9ac0a9aa-a747-4ec3-9065-ef9a51616c3a HTTP/1.1
Host: a3api.wolterskluwer.es
Context: {"clientId": "A3TFR:061602:CM:98d52a37-3659-4d01-a526-750888feb5d9"}
Ocp-Apim-Subscription-Key: 2kxwf5rb7ffb65dd5
Authorization: Bearer eyIkaWQiOiIxIiwidHlwIjoiSld...
{
"correlationId": "9ac0a9aa-a747-4ec3-9065-ef9a51616c3a",
"code": "4300000004",
"description": "CLIENTE TEST",
"state": "Enabled",
"version": "2023-06-01T07:55:14.6539389Z",
"sequenceVersion": "638212029146539385"
}
Desuscribirme de un webhook
Si por cualquier motivo necesitas cancelar la suscripción a un webhook activo, dispones para cada recurso del método unsubscribe
. El request.body de tu petición es el mismo que realizaste para la suscripción:
POST /a3innuva-contabilidad/api/accounts/webhooks/unsubscribe HTTP/1.1
Host: a3api.wolterskluwer.es
context: none
Ocp-Apim-Subscription-Key: 2kxwf5rb7ffb65dd5
Authorization: Bearer eyIkaWQiOiIxIiwidHlwIjoiSld...
Content-Type: application/json
Content-Length: 376
{
"thirdPartyApplicationName": "docuApp",
"url": "https://tudominio.com/endpointwebhooks",
"secret": "d380ca79-feeb-11ed-b90a-7626c28a4e4e",
"eventTypes": [
"a3Innuva.ACC.Accounts.AccountCreated",
"a3Innuva.ACC.Accounts.AccountUpdated",
"a3Innuva.ACC.Accounts.AccountDisabled",
"a3Innuva.ACC.Accounts.ErrorOccurred"
]
}
{
"responseState": 4,
"webhookId": "c0d18eeb-19ba-43e5-a96a-c4c9898b1e0c",
"errors": []
}
Consultar la configuración de un webhook
Puedes consultar la configuración de un webhook realizando una petición GET pasando por querystring el id del webhook:
GET /a3innuva-contabilidad/api/accounts/webhooks/c0d18eeb-19ba-43e5-a96a-c4c9898b1e0c HTTP/1.1
Host: a3api.wolterskluwer.es
context: none
Ocp-Apim-Subscription-Key: 2kxwf5rb7ffb65dd5
Authorization: Bearer eyIkaWQiOiIxIiwidHlwIjoiSld...
{
"id": "c0d18eeb-19ba-43e5-a96a-c4c9898b1e0c",
"thirdPartyApplicationName": "docuApp",
"url": "https://webhook.site/7f414c0e-9526-4abe-8d39-dc3357d38fad",
"tenantId": "2P3N0",
"secret": "d380ca79-feeb-11ed-b90a-7626c28a4e4e",
"isActive": true,
"isDeleted": false,
"events": [
{
"appName": "a3Innuva",
"domain": "ACC",
"resource": "Accounts",
"eventType": "AccountCreated",
"semanticName": "a3Innuva.ACC.Accounts.AccountCreated"
},
{
"appName": "a3Innuva",
"domain": "ACC",
"resource": "Accounts",
"eventType": "AccountUpdated",
"semanticName": "a3Innuva.ACC.Accounts.AccountUpdated"
},
{
"appName": "a3Innuva",
"domain": "ACC",
"resource": "Accounts",
"eventType": "AccountDisabled",
"semanticName": "a3Innuva.ACC.Accounts.AccountDisabled"
},
{
"appName": "a3Innuva",
"domain": "ACC",
"resource": "Accounts",
"eventType": "ErrorOccurred",
"semanticName": "a3Innuva.ACC.Accounts.ErrorOccurred"
}
],
"userIdentifier": "69ad68a1-1150-4697-9208-aec4008d2c98"
}
Habilitar / deshabilitar un webhook
Es posible que necesites temporalmente deshabilitar un webhook, o habilitarlo nuevamente, puedes utilizar el método PUT status
Si el servidor de Webhooks de WK no obtiene respuesta HTTP.200 de tu servidor, tiene establecida una política de reintentos de envío durante un lapso de tiempo determinado. Una vez finalizado el plazo, si tu servidor continúa sin responder con un HTTP.200, se deshabilitará el Webhook.
PUT /a3innuva-contabilidad/api/accounts/webhooks/status/c0d18eeb-19ba-43e5-a96a-c4c9898b1e0c HTTP/1.1
Host: a3api.wolterskluwer.es
context: none
Ocp-Apim-Subscription-Key: 2kxwf5rb7ffb65dd5
Authorization: Bearer eyIkaWQiOiIxIiwidHlwIjoiSld...
Content-Type: application/json
Content-Length: 79
{
"webhookId": "c0d18eeb-19ba-43e5-a96a-c4c9898b1e0c",
"enable": false
}
{
"responseState": 7,
"webhookId": "c0d18eeb-19ba-43e5-a96a-c4c9898b1e0c",
"errors": []
}
Cambiar la url de un webhook
Si quieres cambiar la url a la que enviará las peticiones de POST el servidor de webhooks de WK, puedes modificar el webhook haciendo una petición PUT al método change
PUT /a3innuva-contabilidad/api/accounts/webhooks/change/c0d18eeb-19ba-43e5-a96a-c4c9898b1e0c HTTP/1.1
Host: a3api.wolterskluwer.es
Content-Type: application/json
context: none
Ocp-Apim-Subscription-Key: 2kxwf5rb7ffb65dd5
Authorization: Bearer eyIkaWQiOiIxIiwidHlwIjoiSld...
Content-Length: 90
{
"webhookId": "c0d18eeb-19ba-43e5-a96a-c4c9898b1e0c",
"url": "https://nueva-url"
}
El cambio de la url implica que se debe aceptar un nuevo contrato, por lo que se enviará de nuevo una petición POST del tipo Acknowledge a la nueva url que deberá aceptar tu servidor.
Eliminar un webhook
Si quieres eliminar definitivamente un webhook, debes enviar una petición DELETE al método delete
DELETE /a3innuva-contabilidad/api/accounts/webhooks/delete/c0d18eeb-19ba-43e5-a96a-c4c9898b1e0c HTTP/1.1
Host: a3api.wolterskluwer.es
context: none
Ocp-Apim-Subscription-Key: 2kxwf5rb7ffb65dd5
Authorization: Bearer eyIkaWQiOiIxIiwidHlwIjoiSld...
{
"responseState": 5,
"webhookId": "c0d18eeb-19ba-43e5-a96a-c4c9898b1e0c",
"errors": []
}
Esto marcará el webhook como eliminado:
{
"id": "c0d18eeb-19ba-43e5-a96a-c4c9898b1e0c",
"thirdPartyApplicationName": "docuApp",
"url": "https://nueva-url",
"tenantId": "2P3N0",
"secret": "d380ca79-feeb-11ed-b90a-7626c28a4e4e",
"isActive": false,
"isDeleted": true,
"deletedAt": "2023-06-01T12:30:52.732Z",
"events": [
{
"appName": "a3Innuva",
"domain": "ACC",
"resource": "Customers",
"eventType": "CustomerCreated",
"semanticName": "a3Innuva.ACC.Customers.CustomerCreated"
},
{
"appName": "a3Innuva",
"domain": "ACC",
"resource": "Customers",
"eventType": "CustomerUpdated",
"semanticName": "a3Innuva.ACC.Customers.CustomerUpdated"
},
{
"appName": "a3Innuva",
"domain": "ACC",
"resource": "Customers",
"eventType": "CustomerDisabled",
"semanticName": "a3Innuva.ACC.Customers.CustomerDisabled"
},
{
"appName": "a3Innuva",
"domain": "ACC",
"resource": "Customers",
"eventType": "ErrorOccurred",
"semanticName": "a3Innuva.ACC.Customers.ErrorOccurred"
}
],
"userIdentifier": "69ad68a1-1150-4697-9208-aec4008d2c98"
}
Validar el payload
En el momento de suscribirte a un webhook, debes facilitar un secret
que se utilizará para enviarte en cada payload un header con la firma. Esto es útil para validar que el payload no ha sido modificado durante el envío y que es el mismo valor que el facilitado por WK.
El header wke-webhook-signature
contiene un valor hash generado a partir del payload y el secret proporcionado al suscribirse.
Facilitamos una clase en c# de ejemplo para la validación del hash:
using System.Text;
using System.Globalization;
using System.Security.Cryptography;
namespace WKWebhooks
{
public class WebhookSignatureValidationService
{
private const string SignatureHeaderKey = "sha256";
private const string SignatureHeaderValueTemplate = SignatureHeaderKey + "={0}";
private readonly char[] hexLookup =
{
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
};
public bool IsValid(string webhookSecret, string requestHeaderSignature, string payload)
{
var secret = Encoding.UTF8.GetBytes(webhookSecret);
using (var hasher = new HMACSHA256(secret))
{
var data = Encoding.UTF8.GetBytes(payload);
var sha256 = hasher.ComputeHash(data);
var headerValueShouldBe = string.Format(
CultureInfo.InvariantCulture,
SignatureHeaderValueTemplate,
this.ToHex(sha256));
return headerValueShouldBe.Equals(requestHeaderSignature);
}
}
private string ToHex(byte[] data)
{
if (data == null)
{
return string.Empty;
}
var content = new char[data.Length * 2];
var output = 0;
byte d;
for (var input = 0; input < data.Length; input++)
{
d = data[input];
content[output++] = this.hexLookup[d / 0x10];
content[output++] = this.hexLookup[d % 0x10];
}
return new string(content);
}
}
}