Blog from June, 2022

You can choose to receive notification events about your SMS messaging by registering webhooks. This is a step-by-step guide to registering a webhook to receive notification events when a message is sent, finalized, or received.

Update

You can now register webhooks through our web portal. In Volt Messaging, navigate to the Webhooks tab in your Account Settings to register new webhooks and update existing connections.


Note: during registration, an automated test request is sent to your webhook URL. Your server must return a successful response (2XX status code) to complete registration.

\uD83D\uDCD8 Instructions

First, ensure that you have access to the data necessary for utilizing the Volt GraphQL API:

  1. Have your Volt GraphQL API access token readily available. If you are unsure of where to locate your API token, contact api@textvolt.com.

  2. In order to receive webhooks, you will need a publicly accessible HTTP server capable of listening for POST requests. We recommend implementing HTTPS for securely receiving webhook events.

If you register a webhook URL, notification events will be sent to that URL for any phone number belonging to your organization. Any routing based on the sending or receiving phone number, or any other information relating to a message, should be handled as needed within the server that handles the webhook events.

To register a URL for receiving webhook notification events, use the Volt GraphQL API:

  1. Set the Authorization header to the value Bearer <token>, replacing <token> with the API access token belonging to your organization.

  2. Construct a GraphQL query using the registerWebhook mutation:

    mutation registerWebhook ($url: String!) {
        registerWebhook (url: $url) {
            id
            url
            createdAt
            lastUpdatedAt
        }
    }

  3. Construct a JSON object containing the GraphQL variables for the query created in the previous step:

    {
      "url": "https://example.com/webhooks"
    }

  4. Send a POST request to https://api.respondflow.com/graphql with the GraphQL query and variables in the request body. We recommend using a GraphQL client library in the language of your choice to construct and send GraphQL API requests.

    For example, to send the above GraphQL request using curl:

    curl --location --request POST 'https://api.respondflow.com/graphql' \
    --header 'Authorization: Bearer <token>' \
    --header 'Content-Type: application/json' \
    --data-raw '{"query":"mutation registerWebhook ($url: String!) {\n    registerWebhook (url: $url) {\n        id\n        url\n        createdAt\n        lastUpdatedAt\n    }\n}","variables":{"url":"https://example.com/webhooks"}}'


    Once a webhook is registered, you should receive POST requests containing webhook notification event data related to your SMS messages.

Trouble Shooting

Our server expects to receive a status code 200 from a successful POST request to the registered url, otherwise you will receive this error

Error from GraphQL API: webhook failed to respond successfully

🛠  Example webhook payloads

Webhook payloads of all types are sent to each registered webhook URL. In order to differentiate between different types of webhook notification events, you can inspect the event_type property of the payload object contained within the webhook payload.

When a message is received

{
  "type": "messaging",
  "id": "rfw_6962ccb5_80d23dc13c024d38873534471681379c",
  "payload": {
    "message_app_id": "c7fdc0ba-f3df-4e30-a7fc-397940a6f675",
    "id": null,
    "to_number": "+15555550123",
    "from_number": "+15555550145",
    "valid_until": null,
    "type": "SMS",
    "text": "Replying to message from handset",
    "sent_at": null,
    "received_at": "2022-04-07T22:21:04.259+00:00",
    "status": "webhook_delivered",
    "parts": 1,
    "messaging_profile_id": "40017bf5-8809-4640-9925-da5c732667b7",
    "media": [],
    "encoding": "GSM-7",
    "direction": "inbound",
    "completed_at": null,
    "occurred_at": "2022-04-07T22:21:04.397+00:00",
    "event_type": "message.received"
  },
  "time": 1649370064
}

When a message is sent to carrier

{
  "type": "messaging",
  "id": "rfw_6962ccb5_e41a61024d9a4a61817e7220fbcbf6b4",
  "payload": {
    "message_app_id": "40318006-1cae-4997-acbc-fe9a4aadb7dd",
    "id": "rf_o6aDxhawR227gt5wgZ2I",
    "to_number": "+15555550123",
    "from_number": "+15555550145",
    "valid_until": "2022-04-07T23:19:44.718+00:00",
    "type": "SMS",
    "text": "Sending a message to myself to demonstrate using the GraphQL API",
    "sent_at": "2022-04-07T22:19:44.985+00:00",
    "received_at": "2022-04-07T22:19:44.718+00:00",
    "status": "sent",
    "parts": 1,
    "messaging_profile_id": "40017bf5-8809-4640-9925-da5c732667b7",
    "media": [],
    "encoding": "GSM-7",
    "direction": "outbound",
    "completed_at": null,
    "occurred_at": "2022-04-07T22:19:44.985+00:00",
    "event_type": "message.sent"
  },
  "time": 1649369985
}

When a message is finalized

A message is finalized whenever it has been received by the handset or has failed to deliver. Both cases will be delivered with an event_type of message.finalized. Delivered and failed messages can be differentiated by inspecting the status property of the payload object contained within the webhook payload.

A message may fail to deliver for several reasons, including timeout, spam blocking, receiver opt out, and others.

When a message is delivered

{
  "type": "messaging",
  "id": "rfw_6962ccb5_eb7654690ec04a0593cc645fd2353995",
  "payload": {
    "message_app_id": "40318006-1cae-4997-acbc-fe9a4aadb7dd",
    "id": "rf_o6aDxhawR227gt5wgZ2I",
    "to_number": "+15555550123",
    "from_number": "+15555550145",
    "valid_until": "2022-04-07T23:19:44.718+00:00",
    "type": "SMS",
    "text": "Sending a message to myself to demonstrate using the GraphQL API",
    "sent_at": "2022-04-07T22:19:44.985+00:00",
    "received_at": "2022-04-07T22:19:44.718+00:00",
    "provider_error_code": null,
    "provider_error_detail": null,
    "provider_error_title": null,
    "carrier_name": "T-MOBILE USA, INC.",
    "error_code": null,
    "status": "delivered",
    "parts": 1,
    "messaging_profile_id": "40017bf5-8809-4640-9925-da5c732667b7",
    "media": [],
    "encoding": "GSM-7",
    "direction": "outbound",
    "completed_at": "2022-04-07T22:19:46.119+00:00",
    "occurred_at": "2022-04-07T22:19:46.119+00:00",
    "event_type": "message.finalized"
  },
  "time": 1649369986
}

When a message fails to deliver

{
  "type": "messaging",
  "id": "rfw_6962ccb5_eb7654690ec04a0593cc645fd2353995",
  "payload": {
    "message_app_id": "40318006-1cae-4997-acbc-fe9a4aadb7dd",
    "id": "rf_o6aDxhawR227gt5wgZ2I",
    "to_number": "+15555550123",
    "from_number": "+15555550145",
    "valid_until": "2022-04-07T23:19:44.718+00:00",
    "type": "SMS",
    "text": "Sending a message to myself to demonstrate using the GraphQL API",
    "sent_at": "2022-04-07T22:19:44.985+00:00",
    "received_at": null,
    "provider_error_code": "40002",
    "provider_error_detail": "The message was flagged by a SPAM filter and was not delivered. This is a temporary condition.",
    "provider_error_title": "Blocked as spam - temporary",
    "carrier_name": "T-MOBILE USA, INC.",
    "error_code": "702",
    "status": "failed",
    "parts": 1,
    "messaging_profile_id": "40017bf5-8809-4640-9925-da5c732667b7",
    "media": [],
    "encoding": "GSM-7",
    "direction": "outbound",
    "completed_at": "2022-04-07T22:19:46.119+00:00",
    "occurred_at": "2022-04-07T22:19:46.119+00:00",
    "event_type": "message.finalized"
  },
  "time": 1649369986
}

🔐 Validating webhooks

Each Volt webhook notification event that we send you will include a cryptographic signature in the X-Respond-Flow-Signature header. The signature allows you to validate that webhooks were not sent by a third-party.

When validating the RSA signature included with each webhook notification event request, it is recommended to use an established cryptographic library in the language of your choice. The entire webhook request payload should be used as the message in the RSA signature validation.

Our RSA public key is available at https://callbacks.respondflow.com/id_rsa.pub, which can be used to validate the signature sent with each request using a standard cryptographic library in your language of choice.

This article walks step-by-step through sending an SMS message using the Volt GraphQL API.

We strongly suggest using a GraphQL client library in the language of your choice when interacting with the Volt GraphQL API. Using a GraphQL client library will make it easier to take advantage of static typing, API schema introspection, and other features that let you quickly and reliably build into the Volt GraphQL API. For more information, check out What is a GraphQL client and why would I use one?.

\uD83D\uDCD8 Instructions

First, ensure that you have access to the data necessary for utilizing the Volt GraphQL API:

  1. Have your Volt GraphQL API access token readily available. If you are unsure of where to locate your API token, contact api@textvolt.com.

  2. To send an SMS message using the GraphQL API, you will need to specify the sending and receiving phone numbers, the message content, and a publicly accessible URL for media (if sending MMS).

A phone number can only be sent from if it belongs to your organization. If you are unsure of which phone numbers belong to your organization, log in to Volt or contact api@textvolt.com.

To ensure that phone numbers can be processed correctly by Volt, ensure that any phone numbers are formatted according to the E.164 standard. For example: (555) 555-0123 should be formatted as +15555550123.

Next, use the necessary data to send a message using the GraphQL API. For example, if you want to send an SMS message with the sending number +15555550123 and the receiving number +15555550145, construct a GraphQL API request:

  1. Set the Authorization header to the value Bearer <token>, replacing <token> with the API access token belonging to your organization.

  2. Construct a GraphQL query using the createMessage mutation:

    mutation createMessage ($to: String!, $from: String!, $body: String!, $media: [String!]) {
        createMessage (to: $to, from: $from, body: $body, media: $media) {
            id
            legacyId
            toNumber
            fromNumber
            status
            statusDescription
            media
            body
            sentAt
            confirmedAt
        }
    }

  3. Construct a JSON object containing the GraphQL variables for the query created in the previous step:

    {
      "to": "+15555550145",
      "from": "+15555550123",
      "body": "Your One Time Code is 654312\n\nReply STOP to opt-out.",
      "media": []
    }

    Note: to send an MMS message, set the media variable to a list of publicly-accessible media URLs corresponding to the media you wish to send. Supported file types are: JPEG, PNG, GIF, VCF (vCard).

  4. Send a POST request to https://api.respondflow.com/graphql with the GraphQL query and variables in the request body. We recommend using a GraphQL client library in the language of your choice to construct and send GraphQL API requests.

    For example, to send the above GraphQL request using curl:

    curl --location --request POST 'https://api.respondflow.com/graphql' \
    --header 'Authorization: Bearer <token>' \
    --header 'Content-Type: application/json' \
    --data-raw '{"query":"mutation createMessage ($to: String!, $from: String!, $body: String!, $media: [String!]) {\n    createMessage (to: $to, from: $from, body: $body, media: $media) {\n        id\n        legacyId\n        toNumber\n        fromNumber\n        status\n        statusDescription\n        media\n        body\n        sentAt\n        confirmedAt\n    }\n}","variables":{"to":"+15555550145","from":"+15555550123","body":"Your One Time Code is 654312\n\nReply STOP to opt-out.","media":[]}}'

🛠 Examples by Language

These code snippets exhibit examples of sending SMS using the GraphQL API without using a GraphQL client library.

We strongly suggest using a GraphQL client library in the language of your choice when interacting with the Volt GraphQL API.

In addition, we suggest using a pattern of retrying requests with exponential backoff. While Volt strives to ensure all services achieve 99.99% availability, transient failures are inevitable with any service. Retries allow clients to survive these random partial failures and short-lived transient failures by sending the same request again. Most common HTTP libraries include the ability to retry requests as a built-in feature.

C#

var client = new RestClient("https://api.respondflow.com/graphql");
client.Timeout = -1;
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\"query\":\"mutation createMessage ($to: String!, $from: String!, $body: String!, $media: [String!]) {\\n    createMessage (to: $to, from: $from, body: $body, media: $media) {\\n        id\\n        legacyId\\n        toNumber\\n        fromNumber\\n        status\\n        statusDescription\\n        media\\n        body\\n        sentAt\\n        confirmedAt\\n    }\\n}\",\"variables\":{\"to\":\"+15555550145\",\"from\":\"+15555550123\",\"body\":\"Your One Time Code is 654312\\n\\nReply STOP to opt-out.\",\"media\":[]}}",
           ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
Console.WriteLine(response.Content);

Go

package main

import (
  "fmt"
  "strings"
  "net/http"
  "io/ioutil"
)

func main() {

  url := "https://api.respondflow.com/graphql"
  method := "POST"

  payload := strings.NewReader("{\"query\":\"mutation createMessage ($to: String!, $from: String!, $body: String!, $media: [String!]) {\\n    createMessage (to: $to, from: $from, body: $body, media: $media) {\\n        id\\n        legacyId\\n        toNumber\\n        fromNumber\\n        status\\n        statusDescription\\n        media\\n        body\\n        sentAt\\n        confirmedAt\\n    }\\n}\",\"variables\":{\"to\":\"+15555550145\",\"from\":\"+15555550123\",\"body\":\"Your One Time Code is 654312\\n\\nReply STOP to opt-out.\",\"media\":[]}}")

  client := &http.Client {
  }
  req, err := http.NewRequest(method, url, payload)

  if err != nil {
    fmt.Println(err)
    return
  }
  req.Header.Add("Authorization", "Bearer <token>")
  req.Header.Add("Content-Type", "application/json")

  res, err := client.Do(req)
  if err != nil {
    fmt.Println(err)
    return
  }
  defer res.Body.Close()

  body, err := ioutil.ReadAll(res.Body)
  if err != nil {
    fmt.Println(err)
    return
  }
  fmt.Println(string(body))
}

HTTP

POST /graphql HTTP/1.1
Host: api.respondflow.com
Authorization: Bearer <token>
Content-Type: application/json
Content-Length: 492

{"query":"mutation createMessage ($to: String!, $from: String!, $body: String!, $media: [String!]) {\n    createMessage (to: $to, from: $from, body: $body, media: $media) {\n        id\n        legacyId\n        toNumber\n        fromNumber\n        status\n        statusDescription\n        media\n        body\n        sentAt\n        confirmedAt\n    }\n}","variables":{"to":"+15555550145","from":"+15555550123","body":"Your One Time Code is 654312\n\nReply STOP to opt-out.","media":[]}}

Java

OkHttpClient client = new OkHttpClient().newBuilder()
  .build();
MediaType mediaType = MediaType.parse("application/json");
RequestBody body = RequestBody.create(mediaType, "{\"query\":\"mutation createMessage ($to: String!, $from: String!, $body: String!, $media: [String!]) {\\n    createMessage (to: $to, from: $from, body: $body, media: $media) {\\n        id\\n        legacyId\\n        toNumber\\n        fromNumber\\n        status\\n        statusDescription\\n        media\\n        body\\n        sentAt\\n        confirmedAt\\n    }\\n}\",\"variables\":{\"to\":\"+15555550145\",\"from\":\"+15555550123\",\"body\":\"Your One Time Code is 654312\\n\\nReply STOP to opt-out.\",\"media\":[]}}");
Request request = new Request.Builder()
  .url("https://api.respondflow.com/graphql")
  .method("POST", body)
  .addHeader("Authorization", "Bearer <token>")
  .addHeader("Content-Type", "application/json")
  .build();
Response response = client.newCall(request).execute();

JavaScript (Node)

var request = require('request');
var options = {
  'method': 'POST',
  'url': 'https://api.respondflow.com/graphql',
  'headers': {
    'Authorization': 'Bearer <token>',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    query: `mutation createMessage ($to: String!, $from: String!, $body: String!, $media: [String!]) {
    createMessage (to: $to, from: $from, body: $body, media: $media) {
        id
        legacyId
        toNumber
        fromNumber
        status
        statusDescription
        media
        body
        sentAt
        confirmedAt
    }
}`,
    variables: {"to":"+15555550145","from":"+15555550123","body":"Your One Time Code is 654312\n\nReply STOP to opt-out.","media":[]}
  })
};
request(options, function (error, response) {
  if (error) throw new Error(error);
  console.log(response.body);
});

PHP

<?php

$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => 'https://api.respondflow.com/graphql',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => '',
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 0,
  CURLOPT_FOLLOWLOCATION => true,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => 'POST',
  CURLOPT_POSTFIELDS =>'{"query":"mutation createMessage ($to: String!, $from: String!, $body: String!, $media: [String!]) {\\n    createMessage (to: $to, from: $from, body: $body, media: $media) {\\n        id\\n        legacyId\\n        toNumber\\n        fromNumber\\n        status\\n        statusDescription\\n        media\\n        body\\n        sentAt\\n        confirmedAt\\n    }\\n}","variables":{"to":"+15555550145","from":"+15555550123","body":"Your One Time Code is 654312\\n\\nReply STOP to opt-out.","media":[]}}',
  CURLOPT_HTTPHEADER => array(
    'Authorization: Bearer <token>',
    'Content-Type: application/json'
  ),
));

$response = curl_exec($curl);

curl_close($curl);
echo $response;

Python

import requests

url = "https://api.respondflow.com/graphql"

payload="{\"query\":\"mutation createMessage ($to: String!, $from: String!, $body: String!, $media: [String!]) {\\n    createMessage (to: $to, from: $from, body: $body, media: $media) {\\n        id\\n        legacyId\\n        toNumber\\n        fromNumber\\n        status\\n        statusDescription\\n        media\\n        body\\n        sentAt\\n        confirmedAt\\n    }\\n}\",\"variables\":{\"to\":\"+15555550145\",\"from\":\"+15555550123\",\"body\":\"Your One Time Code is 654312\\n\\nReply STOP to opt-out.\",\"media\":[]}}"
headers = {
  'Authorization': 'Bearer <token>',
  'Content-Type': 'application/json'
}

response = requests.request("POST", url, headers=headers, data=payload)

print(response.text)

Ruby

require "uri"
require "net/http"

url = URI("https://api.respondflow.com/graphql")

https = Net::HTTP.new(url.host, url.port)
https.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = "Bearer <token>"
request["Content-Type"] = "application/json"
request.body = "{\"query\":\"mutation createMessage ($to: String!, $from: String!, $body: String!, $media: [String!]) {\\n    createMessage (to: $to, from: $from, body: $body, media: $media) {\\n        id\\n        legacyId\\n        toNumber\\n        fromNumber\\n        status\\n        statusDescription\\n        media\\n        body\\n        sentAt\\n        confirmedAt\\n    }\\n}\",\"variables\":{\"to\":\"+15555550145\",\"from\":\"+15555550123\",\"body\":\"Your One Time Code is 654312\\n\\nReply STOP to opt-out.\",\"media\":[]}}"

response = https.request(request)
puts response.read_body

Unix shell (curl)

curl --location --request POST 'https://api.respondflow.com/graphql' \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data-raw '{"query":"mutation createMessage ($to: String!, $from: String!, $body: String!, $media: [String!]) {\n    createMessage (to: $to, from: $from, body: $body, media: $media) {\n        id\n        legacyId\n        toNumber\n        fromNumber\n        status\n        statusDescription\n        media\n        body\n        sentAt\n        confirmedAt\n    }\n}","variables":{"to":"+15555550145","from":"+15555550123","body":"Your One Time Code is 654312\n\nReply STOP to opt-out.","media":[]}}'