A webhook in web development is a method of augmenting or altering the behavior of a web page or web application with custom callbacks. These callbacks may be maintained, modified, and managed by third-party users and developers who may not necessarily be affiliated with the originating website or application. - Wikipedia
In Zefort, webhooks can be used to get callbacks when certain events happen, such as new or updated contracts, documents, or event more specific contract field changes. The administrator of a Zefort account can choose which of these events they want to be sent callbacks for and does not need to ingest all types of events.
To not reinvent the wheel, Zefort uses the CloudEvents specifications for sending and wrapping webhooks. CloudEvents specifies both how the service sending and receiving webhooks should handle certain cases. This includes authentication, message structure, response and error codes as well as abuse protection. Zefort follows these specifications wherever possible and follows any new specification changes if they were to arrive.
CloudEvents is a "specification for describing event data in a common way". Zefort specifically uses the Webhook specification, but CloudEvents does support much more than webhooks. Cloudevents takes care of the question "how should things look like" when it comes to messages. What it does not do is specify what data should be encapsulated inside a cloud event, just the metadata.
If there are any questions regarding how to handle messages, we strongly suggest having a look at the specification to learn more about how CloudEvent Webhooks are sent and handled.
An examples of a json structured CloudEvent payload that Zefort sends:
{
"specversion": "1.0",
"id": "a143aeb7-3ec7-49d8-9ff1-a5ab09cb776e",
"source": "zefort/webhook",
"type": "document_created",
"subject": "doc_1Jf6pQrSFevkeyHfT4",
"time": "2022-11-07T14:04:48.519285+00:00",
"data": {
...
},
"obj_type": "document",
"verb": "created",
"api_url": "https://.../api/documents/doc_1Jf6pQrSFevkeyHfT4/",
"ui_url": "https://.../contracts/ct_1K3LlFiyPP/details/doc_1Jf6pQrSF"
}
Some of these fields must exist according to the CloudEvent, such as id
, specversion
, source
.
Some of the fields are optional fields, such as time
and type
.
Lastly there are "extensions" that Zefort provides, such as the verb
, obj_type
, and URLs to objects that are
not specified by Cloudevents at all, but are provided as extra fields.
Attribute | Description |
---|---|
specversion |
CloudEvent specification version |
id |
Unique UUID for an event |
source |
Source service for the CloudEvent |
type |
Event type (obj_type + verb) |
subject |
In Zefort this is the id of the object represented in the data |
time |
Original event timestamp |
data |
Data of the object in the event |
obj_type |
Object type that the event is about |
verb |
Event that happened to the obj |
api_url |
Zefort API endpoint for the object |
ui_url |
Zefort UI URL for the object |
The data that is places in the data
attribute varies depending on the obj_type
.
It follows the same structure as our API for the same type of object.
Type | Structure |
---|---|
contract |
API GET /contract/<id> |
document |
API GET /document/<id> |
attribute |
Slightly extended version of /contract/<id> attributes attribute |
Zefort uses Authorization Request Header Field
authorization for all requests. This means that Zefort sends an Authorization
header with a bearer token
with each request. The token can be used to verify that the requests originate from Zefort and
not a malicious third-party. You can find the token in the integration settings.
Example request:
POST /webhooks HTTP/1.1
Host: yourserver.example.com
Authorization: Bearer 32940t8n3904t80re9gn8wenfg09s840ns490g
The service to which the webhook is sent must respond to each request. The only accepted status codes
that are returned are 200 OK
, 201 Created
or 204 No Content
.
This in accordance with the specification.
The following happens if the service responds in a certain way:
Response | What happens in Zefort |
---|---|
200 OK |
Event marked as delivered |
201 Created |
Event marked as delivered |
204 No Content |
Event marked as delivered |
4XX-5XX (not 410/429) |
Webhook is retried |
429 Too Many Requests |
Webhook is retried according to Retry-After header |
410 Gone |
Integration is disabled |
410 Gone |
Integration is disabled |
3XX (Redirects) |
Integration is disabled |
Timeout (5 sec) |
Integration is disabled |
In the case of a 4xx-5xx response, Zefort will retry to send a message. It will retry for a minimum of 3 days. Zeforts retry logic follows an exponential curve with the maximum time between retries being 1 day. The first retry will be after 1 second, the second after 2s, then 4, 8, 16, 32, 64, 128, and so on. When all retries have been exhausted, the integration will be disabled.
In the case of a 429 response from the service, which is accompanied by a Retry-After
header,
Zefort does a best effort to retry according to the header.
At the moment, Zefort does not support the abuse protection features specified in the CloudEvent Webhook specification. Zefort however manually vets all outgoing endpoints before they can be taken into use as a webhook destination. Adding a new endpoint does not work without first getting a green light from Zefort to use the endpoint.
Zefort does a best effort to send messages in order, it does however not guarantee it. All messages have a timestamp with the time of the event which should be checked if ordering is important. All messages that are retried also keep the same timestamp and ID as in the initial message, meaning that duplicates can be handled by checking if a message has already been handled.
Zefort currently does not support pre-configured rate-limiting. This means that you can not set
a specific rate-limit ahead of getting any messages. Zefort does support sending a 429 response
with a Retry-After
header which can specify how long to wait before retrying again.
We strongly recommend handling webhooks asynchronously as there might be cases where a lot of requests are made at the same time. This will happen for instance when bulk actions are done in Zefort, or a lot of contracts are added at the same time.
This can be due to multiple reasons:
{
"specversion": "1.0",
"id": "8e495872-c96e-4123-a0ad-230e7b2b8a9b",
"source": "zefort/webhook",
"type": "contract_created",
"subject": "ct_1K3LlFiyPPNpKCJ8Qy",
"time": "2022-11-07T14:04:48.508773+00:00",
"data": {
"attributes": {
"title": null,
"end_date": null,
"effective_date": null,
"signed_date": null,
"main_language": null,
"CUSTOM_vatid_001": null
},
"cover_document": null,
"id": "ct_1K3LlFiyPPNpKCJ8Qy",
"incomplete": false,
"num_emails": 0,
"num_files": 0,
"num_users": 1,
"owner": {
"id": "user_1KfvWccrSnlx70g1XF",
"email": "example@zefort.com",
"name": "Thor Doe"
},
"parties": [],
"permission": "full",
"receive_time": "2022-11-07T14:04:48.477557Z",
"related_contracts": [],
"status": "queued",
"is_duplicate": false,
"favorite_of": [],
"last_modified": "2022-11-07T14:04:48.479067Z",
"trashed": false,
"esigns": [],
"num_signed_documents": 0,
"activities": [],
"allow_emails": false,
"documents": [],
"duplicates": [],
"emails": [],
"notes": [],
"num_binders": 0,
"rated": false,
"taggings": []
},
"obj_type": "contract",
"verb": "created",
"api_url": "https://my.zefort.com/api/contracts/ct_1K3LlFiyPPNpKCJ8Qy/",
"ui_url": "https://my.zefort.com/contracts/ct_1K3LlFiyPPNpKCJ8Qy"
}
{
"specversion": "1.0",
"id": "2120e9ce-d0a6-486a-85bf-76e5dea7bd17",
"source": "zefort/webhook",
"type": "contract_updated",
"subject": "ct_1K3LlFiyPPNpKCJ8Qy",
"time": "2022-11-07T14:04:48.741323+00:00",
"data": {
"attributes": {
"title": null,
"end_date": null,
"effective_date": null,
"signed_date": null,
"main_language": null,
"CUSTOM_vatid_001": null
},
"cover_document": {
"id": "doc_1Jf6pQrSFevkeyHfT4",
"content_type": "image/png",
"role": "contract",
"status": "queued",
"num_pages": 0,
"filename": "webhook_test.png",
"receive_time": "2022-11-07T14:04:48.513376Z",
"taggings": [],
"ordinal": 1,
"text_available": false,
"valid_signature": null,
"is_signable": null,
"signatures": []
},
"id": "ct_1K3LlFiyPPNpKCJ8Qy",
"incomplete": false,
"num_emails": 0,
"num_files": 1,
"num_users": 1,
"owner": {
"id": "user_1KfvWccrSnlx70g1XF",
"email": "example@zefort.com",
"name": "Saga Doe"
},
"parties": [],
"permission": "full",
"receive_time": "2022-11-07T14:04:48.477557Z",
"related_contracts": [],
"status": "processing",
"is_duplicate": false,
"favorite_of": [],
"last_modified": "2022-11-07T14:04:48.617280Z",
"trashed": false,
"esigns": [],
"num_signed_documents": 0,
"activities": [],
"allow_emails": false,
"documents": [
{
"id": "doc_1Jf6pQrSFevkeyHfT4",
"content_type": "image/png",
"role": "contract",
"status": "queued",
"num_pages": 0,
"filename": "webhook_test.png",
"receive_time": "2022-11-07T14:04:48.513376Z",
"taggings": [],
"ordinal": 1,
"text_available": false,
"valid_signature": null,
"is_signable": null,
"signatures": []
}
],
"duplicates": [],
"emails": [],
"notes": [],
"num_binders": 0,
"rated": false,
"taggings": []
},
"obj_type": "contract",
"verb": "updated",
"api_url": "https://my.zefort.com/api/contracts/ct_1K3LlFiyPPNpKCJ8Qy/",
"ui_url": "https://my.zefort.com/contracts/ct_1K3LlFiyPPNpKCJ8Qy"
}
{
"specversion": "1.0",
"id": "a143aeb7-3ec7-49d8-9ff1-a5ab09cb776e",
"source": "zefort/webhook",
"type": "document_created",
"subject": "doc_1Jf6pQrSFevkeyHfT4",
"time": "2022-11-07T14:04:48.519285+00:00",
"data": {
"id": "doc_1Jf6pQrSFevkeyHfT4",
"content_type": "image/png",
"role": "contract",
"status": "queued",
"num_pages": 0,
"filename": "webhook_test.png",
"receive_time": "2022-11-07T14:04:48.513376Z",
"taggings": [],
"ordinal": 1,
"text_available": false,
"valid_signature": null,
"is_signable": null,
"signatures": [],
"contract": "ct_1K3LlFiyPPNpKCJ8Qy"
},
"obj_type": "document",
"verb": "created",
"api_url": "https://my.zefort.com/api/documents/doc_1Jf6pQrSFevkeyHfT4/",
"ui_url": "https://my.zefort.com/contracts/ct_1K3LlFiyPPNpKCJ8Qy/details/doc_1Jf6pQrSFevkeyHfT4"
}
{
"specversion": "1.0",
"id": "6ffc083f-e9c3-4b11-8779-9b3874785a91",
"source": "zefort/webhook",
"type": "attribute_created",
"time": "2022-11-08T07:00:34.361888+00:00",
"data": {
"contract": "ct_1L3XJMbGQcyEtlzF7R",
"name": "title",
"label": "Title",
"value": "A new title",
"timestamp": "2022-11-08T07:00:34.355977Z",
"source": "",
"from_default": null,
"set_by": "user_1KfvWccrSnlx70g1XF"
},
"obj_type": "attribute",
"verb": "created",
"api_url": "https://my.zefort.com/api/contracts/ct_1L3XJMbGQcyEtlzF7R/",
"ui_url": "https://my.zefort.com/contracts/ct_1L3XJMbGQcyEtlzF7R"
}