Schedule or defer a webhook
You can defer or schedule webhooks so that they return information at different times.
Deferred webhooks
You can set your webhooks to only respond after a certain time and/or when an object matches the specific criteria that you want to appear in your webhook.
The following example demonstrates how to create a deferred webhook that fires 10 seconds after the job has been created. It will not fire if the job Description
field is cancel
or changes to cancel
within 10 seconds of the job being created.
-
Modify your
webhook.js
file to add the following configuration:const url = "https://b14b2804.ngrok.io" const jsonDeferred = { name: "test_deferred", url: url, type: "graphql_deferred", field: "CreatedDate", offset: 10000, filter: "Description != 'cancel'", query: ` subscription { jobs { UID Name Description Duration Start End CreatedDate JobAllocations { UID } } } ` } console.log(JSON.stringify(jsonDeferred, null, 2))
-
The
field
must be a field with anINSTANT
value, such asStart
,End
,ActualStart
,EstimatedEnd
, and so on. In the case above, the webhook is being deferred on the basis of theCreatedDate
value. -
The
offset
is used to determine when the webhook fires. It is a number of milliseconds relative to the value of the timestamp contained infield
above. In the example, the webhook will fire10000
milliseconds after the job is created. Theoffset
value can also be negative for situations where a webhook should be fired before a timestamp in the future. For example, sending an SMS to a customer 24 hours prior to theJob
’sStart
time. Note that there is a small delay between when a data change is processed and when any related webhooks are updated. For this reason, webhooks relative toCreatedDate
orLastModifiedDate
should have anoffset
of at least 5000ms. -
The
filter
field is used to filter jobs so that the webhook will only fire when relevant conditions are met. In this case, jobs withcancel
in theDescription
will not cause a webhook to fire.Note
For deferred webhooks, the filter field must be provided.
-
-
Create a webhook request body:
node webhook.js > temp.json
-
Check that the
temp.json
file now contains thejsonDeferred
GraphQL query. The file should include the following configuration:{ "name": "test_deferred", "url": "https://b14b2804.ngrok.io", "type": "graphql_deferred", "field": "CreatedDate", "offset": 10000, "filter": "Description != 'cancel'", "query": "\n subscription {\n jobs {\n UID\n Name\n Description\n Duration\n Start\n End\n CreatedDate\n JobAllocations {\n UID\n }\n }\n }\n " }
-
Create the deferred webhook using the cURL command:
curl -s -X POST -H "Authorization: Bearer $AUTH_TOKEN" -H "Content-Type: application/json" -d @temp.json 'https://api.skedulo.com/webhooks' | jq
This returns the following response confirming that the webhook is established, including the webhook
id
:{ "result": { "id": "6b6d32e0-ad1f-45fa-9f03-b5264ca02ecb", "name": "test_deferred", "url": "https://b14b2804.ngrok.io", "headers": {}, "schemaName": "Jobs", "fieldName": "CreatedDate", "offset": 10000, "filter": "Description != 'cancel'", "query": "\n subscription {\n jobs {\n UID\n Name\n Description\n Duration\n Start\n End\n CreatedDate\n JobAllocations {\n UID\n }\n }\n }\n ", "customFields": {}, "type": "graphql_deferred" } }
-
Create a new job using GraphQL to confirm that the webhook has been created with a deferred
offset
of 10000 milliseconds (10 seconds).If you still have the
test
webhook running, you will receive two responses for this job in the terminal that is listening on port8080
.-
The first response is from the
test
webhook, which fires the response immediately when the job is created. -
The second response is sent 10 seconds later from the
test_deferred
webhook. Both responses are identical except for theskedulo-webhook-id
andskedulo-request-id
numbers that the webhook that sent the request.
-
-
Confirm that the webhook filters jobs with
cancel
in theDescription
field:a. Create a new job or update an existing job to have the value
cancel
in the subscription field.b. The deferred webhook will not show a response as jobs with
cancel
in theDescription
field are being ignored.
If your test
webhook is still active, you will still receive an immediate response from that webhook.
Configure dynamically deferred webhooks
Configure deferred webhooks to respond to offset values assigned to custom fields on objects.
This provides object-level control over when the webhook fires.
For example, you can set a custom field on a job with an offset that triggers an SMS to the customer one day before the job to remind them of their appointment. The offset allows the “one day” to differ between jobs and is configurable and hard coded at an organizational level.
When creating a webhook, the offset
field on an object can also be an object with two fields:
field
: is the name of a number or duration field on the schema.default
: is an optional value used as the offset if thefield
has no value.
In the following example, we create a custom number field on the Shifts
object called CustomNumber
, then create a webhook with a default offset value of 15 seconds (15000
).
The webhook will send a notification for the shift after 15 seconds unless a different CustomNumber
value is provided on the shift.
Example: Deferred webhook on the Shifts
object using a dynamic offset
About ngrok
For development purposes, this chapter uses ngrok to establish a secure HTTP tunnel to a server running on your local machine.
Skedulo only permits HTTPS URLs for webhooks.
-
Create a custom number field on the
Shifts
object calledCustomNumber
. Use the following payload in aPOST
request to the/custom/standalone/fields
endpoint:{ "name": "CustomNumber", "schemaName": "Shifts", "column": { "type": "int" } }
-
Create a file called
httpserver.js
with the following configuration:const http = require("http"); http.createServer((req, res) => { const body = []; req .on('data', (chunk) => { body.push(chunk); }) .on('end', () => { const bodyStr = Buffer.concat(body).toString(); const obj = { headers: req.headers, body: JSON.parse(bodyStr) } console.log(JSON.stringify(obj, null, 2)) res.write(JSON.stringify(obj, null, 2)) res.end() }); res.writeHead(200, {"Content-Type": "application/json"}) }).listen(8080)
-
Start the HTTP server in one terminal:
node httpserver.js
-
Start ngrok in another terminal:
./ngrok http 8080
Take note of the HTTPS address of your ngrok server.
Use this address as the
"url"
in the webhook file in the webhook configuration file in Step 5. -
Create a webhook with an offset field value of 15 seconds (
15000
):{ "name": "graphql_deferred_offset_field_shift", "url": "https://b64dd386.ngrok.io", "type": "graphql_deferred", "field": "Start", "offset": { "field": "CustomNumber", "default": 15000 }, "filter": "DisplayName != 'cancel'", "query": "\nsubscription {\n shifts {\n UID\n DisplayName\n Duration\n Start\n End\n CustomNumber\n CreatedDate\n }\n}\n" }
The following response shows the webhook has been created successfully with the
CustomNumber
offset field name on theShifts
object and theoffset
value of 15 seconds (15000
):{ "result": { "id": "b345c228-9c19-48c4-a27c-d9cb489c30d7", "name": "graphql_deferred_offset_field_shift", "url": "https://b64dd386.ngrok.io", "headers": {}, "schemaName": "Shifts", "fieldName": "Start", "offset": { "fieldName": "CustomNumber", "default": 15000 }, "filter": "DisplayName != 'cancel'", "query": "\nsubscription {\n shifts {\n UID\n DisplayName\n Duration\n Start\n End\n CustomNumber\n CreatedDate\n }\n}\n", "customFields": { "Shifts": [ "CustomNumber" ] }, "type": "graphql_deferred" } }
The default on the
offset
object is optional, and is used if thefield
has no value. If thefield
has no value and nodefault
is provided then the webhook is not triggered for that object. -
Create two shifts using GraphQL, one with a custom number offset and the other without:
mutation { schema { shift1: insertShifts(input: { RegionId: "00036206-7555-4280-b1b7-86d566437391" DisplayName: "Shift no custom number" CustomNumber: null Start: "2020-01-16T20:22:00Z" Duration: 60 }) shift2: insertShifts(input: { RegionId: "00036206-7555-4280-b1b7-86d566437391" DisplayName: "Shift custom number" CustomNumber: 10000 Start: "2020-01-16T20:22:00Z" Duration: 60 }) } }
-
Check the terminal that is listening on port
8080
to see the response:{ "result": [ { "timestamp": "2020-01-16T20:22:15.825909Z", "level": "WARN", "message": "Webhook request returned status code 404", "data": { "webhookId": "42170bdd-2c09-47ca-b484-8a02e705b9ce", "webhookRequestId": "d563e37c-e468-4b66-a9bd-36ff7c1ddad1" } }, { "timestamp": "2020-01-16T20:22:11.131707Z", "level": "WARN", "message": "Webhook request returned status code 404", "data": { "webhookId": "42170bdd-2c09-47ca-b484-8a02e705b9ce", "webhookRequestId": "6d8aad32-2306-47ef-a491-5ca5b33ac8e3" } }, ] }
Scheduled webhooks
You can also schedule a webhook to fire at certain intervals using cron.
Cron is a time-based scheduling tool that allows you to configure events to run at fixed times or intervals so that you can automate certain operations.
The "cron"
field in a scheduled webhook request expects a cron expression where *
represents a unit of time field. Units of time are represented in the following way:
# ┌───────────── minute (0 - 59)
# │ ┌───────────── hour (0 - 23)
# │ │ ┌───────────── day of the month (1 - 31)
# │ │ │ ┌───────────── month (1 - 12)
# │ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday.
# │ │ │ │ │ Sunday can also be 7.)
# │ │ │ │ │
# │ │ │ │ │
# * * * * *
For example, to run a job every Friday at 11.45AM, the cron expression would look like this:
45 11 * * 5
To create a scheduled webhook, you can include the "cron"
field in the JSON request body with the time or interval you want to schedule the request included using the above format.
For example, the following webhook requests a response from the tenant organization every minute:
Method: POST
Endpoint: /webhooks
Request body:
{
"name": "scheduled_every_minute",
"url": "https://45d2e3b7.ngrok.io",
"headers": {},
"cron": "* * * * *",
"type": "scheduled"
}
The webhook returns a response from the server every minute until the webhook is deleted:
{
"headers": {
"user-agent": "Skedulo-Webhook",
"skedulo-webhook-id": "2c3d8594-3d66-4e9e-ada1-fde48f37fc88",
"skedulo-request-id": "a201143a-e36c-4420-ad8a-1787c1e5f7bb",
"content-length": "2",
"content-type": "application/json; charset=UTF-8",
"accept-encoding": "gzip, deflate",
"host": "45d2e3b7.ngrok.io",
"accept": "*/*",
"x-forwarded-proto": "https",
"x-forwarded-for": "52.33.155.238"
},
"body": {}
}
{
"headers": {
"user-agent": "Skedulo-Webhook",
"skedulo-webhook-id": "2c3d8594-3d66-4e9e-ada1-fde48f37fc88",
"skedulo-request-id": "891a3b8e-67e8-4831-a316-9bdfa83b2e12",
"content-length": "2",
"content-type": "application/json; charset=UTF-8",
"accept-encoding": "gzip, deflate",
"host": "45d2e3b7.ngrok.io",
"accept": "*/*",
"x-forwarded-proto": "https",
"x-forwarded-for": "52.33.155.238"
},
"body": {}
}
{
"headers": {
"user-agent": "Skedulo-Webhook",
"skedulo-webhook-id": "2c3d8594-3d66-4e9e-ada1-fde48f37fc88",
"skedulo-request-id": "dfb3d5b4-d2f7-4582-9edd-fe4455961658",
"content-length": "2",
"content-type": "application/json; charset=UTF-8",
"accept-encoding": "gzip, deflate",
"host": "45d2e3b7.ngrok.io",
"accept": "*/*",
"x-forwarded-proto": "https",
"x-forwarded-for": "52.33.155.238"
},
"body": {}
}
{
"headers": {
"user-agent": "Skedulo-Webhook",
"skedulo-webhook-id": "2c3d8594-3d66-4e9e-ada1-fde48f37fc88",
"skedulo-request-id": "7e04be33-2b69-4110-a807-b45dccbde19c",
"content-length": "2",
"content-type": "application/json; charset=UTF-8",
"accept-encoding": "gzip, deflate",
"host": "45d2e3b7.ngrok.io",
"accept": "*/*",
"x-forwarded-proto": "https",
"x-forwarded-for": "52.33.155.238"
},
"body": {}
}
Feedback
Was this page helpful?