Manage incoming SMS with webhooks
You can use webhooks to route incoming SMS messages sent from your customers or resources in the field.
For example, you might want to send a survey at the end of a job to the customer to rate their experience and have their responding SMS update the job with their feedback.
Alternatively, you might want a resource to be able to check in to start a job by sending an SMS to a number assigned to your organization.
Prerequisites
SMS messaging numbers must be obtained through Skedulo to ensure that it is able to communicate with our internal endpoints and that your incoming messages are able to be routed correctly. To request one, contact your Skedulo Customer Success Manager.
Skedulo sends and receives SMS through an underlying messaging provider, and your tenant is set up with one of two kinds of number. A dedicated number is one Skedulo purchases for your organization and preconfigures as your tenant’s own number for inbound SMS; every message sent to it is yours, and it’s what the rest of this page sets up. A shared number is one your tenant sends and receives through alongside other Skedulo tenants, pooled across a number or set of numbers. This is the default if you haven’t requested a dedicated number, and the number alone doesn’t identify which tenant a message belongs to.
This matters because of a second distinction: a solicited message is a reply to something you, or Skedulo on your behalf (a job confirmation or allocation notification), sent first. An unsolicited message is one where a resource or customer texts your number directly, with nothing earlier to reply to.
On a shared number, only solicited replies can reach you: Skedulo attributes them by matching against the outstanding message they answer, as described in Correlate a reply with the message it answers below. An unsolicited message on a shared number has no message to match and no number-based attribution either, so it can’t be identified as yours and never reaches your webhook. If you need to receive unsolicited messages, such as a resource checking in by texting your number, you need a dedicated number.
Create an inbound SMS webhook
This example uses ngrok for demonstration purposes only. It is assumed that you have your own server infrastructure that you can configure to receive webhook responses. See the Use webhooks section for more information about setting up webhooks for Skedulo.
-
Start your server and confirm the HTTPS address. Only secure HTTPS URLs are accepted for Skedulo webhooks.
-
Create a webhook state file called
inbound-sms.webhook.json:
{
"metadata": { "type": "Webhook" },
"name": "inbound_sms",
"webhook": {
"url": "https://ea8d656e.ngrok.io",
"type": "inbound_sms"
}
}
- Deploy the webhook from the state file:
sked artifacts webhook upsert -f inbound-sms.webhook.json
- Confirm it was created by finding the webhook in the list of configured webhooks:
sked artifacts webhook list --name inbound_sms
- As per the prerequisites you will need the SMS number from Skedulo. When an SMS is sent to that number, the webhook receives the following information, with the contents of the inbound SMS shown in the
body:
{
"key": {
"id": "message_sid_001",
"vendor": "<vendor-name>"
},
"to": "+61411123456",
"from": "+61411222333",
"body": "msg body 1 a",
"receivedAt": "2019-09-24T05:57:49.323Z"
}
- Delete the webhook when it’s no longer needed:
sked artifacts webhook delete --name inbound_sms
Correlate a reply with the message it answers
When you send a message and expect a reply, set expectsReply to true in the payload of the POST /sms API. The response includes an sms key identifying the message:
{
"success": true,
"sms": {
"id": "message_sid_001",
"vendor": "<vendor-name>"
}
}
Note
New Zealand numbers are served by a legacy service that doesn’t support replies, so nosms key is returned for them.
If the recipient replies to the message you sent, the webhook payload will include a property called inReplyTo that links back to the sms.id field you obtained when you sent the message to them.
{
"key": {
"id": "message_sid_002",
"vendor": "<vendor-name>"
},
"to": "+61411123456",
"from": "+61411222333",
"body": "yes please",
"receivedAt": "2019-09-24T05:57:49.323Z",
"inReplyTo": {
"id": "message_sid_001",
"vendor": "<vendor-name>"
}
}
Skedulo treats an inbound SMS as one of two kinds of conversation: a solicited reply to something you sent, or an unsolicited message with nothing to reply to.
inReplyTo is present when the inbound message completes an outstanding expectsReply message.
inReplyTo is absent when:
- An outstanding job notification on the same number wins the match instead; here too, the message the reply would have answered stays open.
- Skedulo can’t process the reply.
- The original message was an opt-out.
- More than 28 days have passed since the original message was sent.
- The original message failed to deliver.
Note
Once yourinbound_sms webhook is configured, it receives every inbound message resolved to your tenant — including replies to Skedulo’s own built-in SMS features, such as a customer’s Y/YES or N/NO to a job confirmation request or a resource’s reply to accept or decline a dispatched job, not only messages you sent yourself. These are solicited replies like any other, and can carry their own inReplyTo. Be ready to see, and ignore, messages your integration didn’t originate.
Note
If more than oneexpectsReply message is outstanding to the same number, a single incoming reply closes all of them, but inReplyTo names only the most recently sent one. It’s recommended to try and keep at most one outstanding expectsReply message per number, per tenant, at a time.
An unsolicited message has no outbound message to correlate to, so inReplyTo never applies — Skedulo attributes it to your tenant by matching the number it arrived on, which is why, as covered in Prerequisites, this only works on a dedicated number. To route it, match the from number to a resource or contact record, then parse the message body for correlating content, such as a job name. The same manual approach applies to any solicited reply where inReplyTo isn’t present.
Opt-out, opt-in, and help keywords
Skedulo sends and receives your inbound SMS through its underlying messaging provider, so the provider’s own carrier-level compliance behavior applies before a message ever reaches Skedulo. The provider recognizes STOP as an opt-out keyword, START as an opt-in keyword, and HELP as a help keyword. STOP adds the sender to the provider’s block list and suppresses future messages to that number. START removes them from the block list and resumes messages. HELP doesn’t touch the block list at all. It just triggers the provider’s automated help reply. For all three, the provider sends its own automated reply, so Skedulo does not send one.
By default, these messages aren’t delivered to your inbound_sms webhook, so you have no way to sync your own suppression list. To have them delivered instead, turn on Forward opt-out messages (such as STOP, START, HELP) to your inbound SMS webhook under Settings > Developer tools > Inbound SMS.
Turning this on only forwards opt-out messages that Skedulo can attribute to your tenant. On a shared number, an opt-out is attributed the same way a solicited reply is: by matching it against an outstanding message sent to that number.
Once enabled, a matching message includes an optOutType field set to the keyword:
{
"key": {
"id": "message_sid_003",
"vendor": "<vendor-name>"
},
"to": "+61411123456",
"from": "+61411222333",
"body": "STOP",
"receivedAt": "2019-09-24T05:57:49.323Z",
"optOutType": "STOP"
}
optOutType reflects the messaging provider’s canonical keyword, not the exact text sent: UNSUBSCRIBE, CANCEL, and QUIT all arrive as STOP. Match on optOutType, not body.
Messages that don’t match a recognized keyword are unaffected and never include optOutType.
The keyword list belongs to the messaging provider’s own opt-out configuration, and Skedulo can’t read or change it. It isn’t limited to the keywords above mapping to STOP, START, and HELP either; the provider may recognize others. Treat optOutType as an open string and handle any value you don’t recognize, rather than assuming it’s always one of these three.
Feedback
Was this page helpful?