Webhooks
Sometimes when you're integrating with Sentera's API and you have users that interact with Sentera via our Web, Desktop, or iOS applications, you'll want to know when data changes in your FieldAgent organization. Sentera's webhook support allows you to receive notifications via HTTP POST when certain events occur such as TASK_MODIFIED. The list of notifications / events will grow over time.
You start by registering an organization whose events you are interested in. Webhook events bubble up an organization hierarchy, so if you have a hierarchy of multiple organizations in the FieldAgent system, and register an organization, you'll receive events produced by data changes within that organization as well as in any of its descendant organizations.
Examples
Step 1 - Get the list of your organizations
In order to register an organization to receive events, you'll first need to get that organization's ID. You can use the organizations query to get that information.
Query
query OrganizationsQuery {
organizations(
pagination: { page: 1, page_size: 10}
) {
total_count
results {
sentera_id
name
}
}
}
Results
{
"data": {
"organizations": {
"total_count": 3,
"results": [
{
"sentera_id": "im9b67y_OR_ecg4Acme_CV_deve_87574b995_220203_121010",
"name": "Acme"
},
{
"sentera_id": "k02cseg_OR_gd2jAcmeChild_CV_deve_87574b995_220203_121010",
"name": "Acme Child"
},
{
"sentera_id": "3sxqcro_OR_koviAcmeGrand_CV_deve_87574b995_220203_121010",
"name": "Acme Grandchild"
}
]
}
}
}
Step 2 - Subscribe to events for a specific organization
Now that you have IDs of your organizations, can register one of those organizations to receive events when data changes in that organization or any descendant organizations. You'll use the create_webhook_subscription mutation to do this. In this example, we'll register the Acme Child organization, which will allow us to receive events for that organization as well as its Acme Grandchild child organization. This webhook subscription will not receive events from the Acme organization as it is the parent of the organization being registered here.
Note that there is no limit to the number of webhook subscriptions you can create for your organizations.
Query
mutation CreateWebhookSubscription {
create_webhook_subscription(
organization_sentera_id: "k02cseg_OR_gd2jAcmeChild_CV_deve_87574b995_220203_121010"
callback_url: "https://example.com/webhooks/sentera"
event_types: [
TASK_MODIFIED,
SURVEY_CREATED,
MOSAIC_CREATED,
FEATURE_SET_CREATED
]
) {
sentera_id
callback_url
event_types
}
}
Results
{
"data": {
"create_webhook_subscription": {
"sentera_id": "s6xzeso_WS_gd2jAcmeChild_CV_deve_94cafb07c_220204_102829",
"callback_url": "https://example.com/webhooks/sentera",
"event_types": [
"TASK_MODIFIED",
"SURVEY_CREATED",
"MOSAIC_CREATED",
"FEATURE_SET_CREATED"
]
}
}
}
Step 3 - Process webhook events
You'll receive events to the endpoint you register via HTTP POST. Any 2xx response will be considered successful by our server. If you return any other response code it will be viewed as an error state and trigger the FieldAgent system to try and resend the event. The system will make at most 4 attempts to send any individual event before giving up. The more failures it receives it will increase the interval between retries in an attempt to avoid overloading your server.
Webhook Request Body
{
"event_id": "ds29dd6_WE__CV_stag_466979d2_220204_190654",
"event_type": "TASK_MODIFIED",
"event_details": {
"sentera_id": "dlhsmml_TA_koviAcmeGrand_CV_deve_87574b995_220203_121016",
"created_by_id": "l9wnbbb_US_koviAcmeGrand_CV_deve_87574b995_220203_121011",
"updated_by_id": "l9wnbbb_US_koviAcmeGrand_CV_deve_87574b995_220203_121011"
}
}
Failed Events
It is possible to retrieve a list of events for your subscription(s) that failed to send correctly.
Query
query OrganizationQuery {
organizations(
pagination: { page: 1, page_size: 100}
) {
total_count
results {
sentera_id
name
webhook_subscriptions(
pagination: { page: 1, page_size: 100}
) {
total_count
results {
sentera_id
event_types
events(
pagination: {page:1, page_size: 100}, status:FAILED
) {
total_count
results {
sentera_id
status
payload {
__typename
sentera_id
created_by_id
updated_by_id
}
}
}
}
}
}
}
}
Results
{
"data": {
"organizations": {
"total_count": 3,
"results": [
{
"sentera_id": "im9b67y_OR_ecg4Acme_CV_deve_87574b995_220203_121010",
"name": "Acme",
"webhook_subscriptions": {
"total_count": 0,
"results": [
]
}
},
{
"sentera_id": "k02cseg_OR_gd2jAcmeChild_CV_deve_87574b995_220203_121010",
"name": "Acme Child",
"webhook_subscriptions": {
"total_count": 1,
"results": [
{
"sentera_id": "6jso51d_WS_koviAcmeGrand_CV_deve_87574b995_220203_121016",
"event_types": [
"TASK_MODIFIED"
],
"events": {
"total_count": 1,
"results": [
{
"sentera_id": "qr29dd6_WE_koviAcmeGrand_CV_stag_466979d2_220204_190654",
"status": "FAILED",
"payload": {
"__typename": "TaskModifiedPayload",
"sentera_id": "zlxsmml_TA_koviAcmeGrand_CV_deve_87574b995_220203_121016",
"created_by_id": "l9wnbbb_US_koviAcmeGrand_CV_deve_87574b995_220203_121011",
"updated_by_id": "l9wnbbb_US_koviAcmeGrand_CV_deve_87574b995_220203_121011"
}
}
]
}
}
]
}
},
{
"sentera_id": "3sxqcro_OR_koviAcmeGrand_CV_deve_87574b995_220203_121010",
"name": "Acme Grandchild",
"webhook_subscriptions": {
"total_count": 0,
"results": [
]
}
}
]
}
}
}
Resend Failed Event
You can resend a failed event using the resend_webhook_event mutation.
Query
mutation ResendWebhookEvent {
resend_webhook_event(
sentera_id: "qr29dd6_WE_koviAcmeGrand_CV_stag_466979d2_220204_190654"
) {
sentera_id
}
}
Results
{
"data": {
"resend_webhook_event": {
"sentera_id": "rul414j_WE__CV_deve_8995b58b8_220131_085259"
}
}
}
Signed Requests
When sending an event to your webhook endpoint, Sentera's server will include a X-Payload-Signature header in the request. This header contains a base 64 encoded signature that was generated from the event payload using a SHA-256 and RSA encryption algorithm. You can use this signature to verify that the request came from Sentera and it wasn't tampered with during transit.
The value in the X-Payload-Signature header has a "RSA-SHA256;<signature>" format, where <signature> is the signature calculated from the event's payload.
While verifying the signature is completely optional and not required to access the event payload, it is a good idea from a security standpoint to make sure the request came from Sentera and wasn't tampered with before you accept the request.
Public Signing Key
First you'll need to obtain a public key that can be used to verify the signature. This key can be obtained from Sentera using a webhook_public_key endpoint for whatever FieldAgent environment you are using:
- Production: https://api.sentera.com/webhook_public_key
- Non-Production: https://apistaging.sentera.com/webhook_public_key
Verify the Signature
Here is an example in Ruby showing how a client can the verify the signature.
require 'openssl'
require 'base64'
require 'json'
# Signature sent in the X-Payload-Signature header following the "RSA-SHA256;" prefix
signature = "<signature>"
# Payload sent in the webhook body
payload = {
...
}.to_json
# Webhook signing key obtained from the webhook_public_key endpoint
public_key = <<~SIGNING_KEY
-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----
SIGNING_KEY
decoded_signature = Base64.strict_decode64(signature)
digest = OpenSSL::Digest.new('sha256', payload)
rsa = OpenSSL::PKey::RSA.new(public_key)
# If this returns true, then the signature has been successfully verified
rsa.verify(digest, decoded_signature, payload)
Authenticated Requests
Sentera optionally supports the OAuth2 client credentials flow to authenticate requests to your API from our webhooks.
First you need to include the authentication parameter to the create_webhook_subscription mutation.
You'll need to provide the client_id, client_secret, and token_endpoint parameters. The token_endpoint is the
URL that we need to use to get an access_token. We only use the token received for the duration of that request and will
ask for a new token each time we send a webhook request.
Authentication Errors
If we receive an error while attempting to retrieve an access token we will send an email to the user who registered the subscription and abort sending the event.
Query
mutation CreateWebhookSubscription {
create_webhook_subscription(
organization_sentera_id: "k02cseg_OR_gd2jAcmeChild_CV_deve_87574b995_220203_121010"
callback_url: "https://example.com/webhooks/sentera"
event_types: [
TASK_MODIFIED,
SURVEY_CREATED,
MOSAIC_CREATED,
FEATURE_SET_CREATED
]
authorization: {
client_id: 'client-id-1234'
client_secret: 'ab32xda12vI)'
token_endpoint: 'https://example.com/oauth/token'
}
) {
sentera_id
callback_url
event_types
}
}
Results
{
"data": {
"create_webhook_subscription": {
"sentera_id": "s6xzeso_WS_gd2jAcmeChild_CV_deve_94cafb07c_220204_102829",
"callback_url": "https://example.com/webhooks/sentera",
"event_types": [
"TASK_MODIFIED",
"SURVEY_CREATED",
"MOSAIC_CREATED",
"FEATURE_SET_CREATED"
]
}
}
}