Webhooks

Instead of polling the Smile API for updates, your app can subscribe to webhook topics in order to receive events as they occur within Smile.

Webhooks must be used with HTTPS. Webhooks are insecure if they are delivered without TLS. Only use HTTPS for your webhook URLs.

Creating a webhook

Webhooks can be created via the app settings page in the Smile Partner Dashboard.

Upon the creation of a Webhook, Smile will POST a test webhook to the supplied URL. This webhook will have the ping topic. In order for the webhook to be created, the endpoint must respond with a 2xx HTTP status code. This is a simple smoke test that shows Smile your app is capable of receiving a webhook.

Topics

Webhook topics allow an app to subscribe to specific events that occur in Smile. Below is a list of webhook topics that an app can subscribe to.

Topic

Description

ping

This is a pseudo topic that gets sent by Smile to check if your webhook endpoint is functioning properly. The data portion of this webhook is an empty object.

customer/updated

Occurs when a customer is updated. This includes the initial creation of a customer.

Structure

Webhooks have a consistent structure across all topics. They contain the following parameters.

Parameter

Description

account_id

The ID of the account on which the event occurred.

data

The specific data pertaining to the webhook topic. For example, the data in a customer/updated webhook will contain a customerobject.

topic

The webhook topic. Topics contain two components: a subject and a verb separated by a slash (e.g. customer/updated).

Here is an example webhook body.

{
"account_id": 0,
"data": {},
"topic": "ping"
}

Customer webhooks

For webhook topics with a "customer" subject (e.g. customer/updated), the data attribute in the webhook body will contain the following parameters.

Parameter

Description

customer

An object representing the customer that was updated. See the customer object for the attributes that are included.

Verifying signatures

In order to verify that a webhook contains legitimate data, Smile signs the contents of each webhook with a sha256 HMAC. Webhooks are signed using the app's client secret which can be found in the app settings page of the Smile Partner Dashboard.

The following headers must be utilized to verify the signature of a webhook.

Header

Description

Smile-Signature

The HMAC generated by Smile. An app will attempt to compute this signature in order to verify that the webhook is legitimate.

Smile-Timestamp

The number of seconds since the Unix epoch (UTC). This value is used to prevent replay attacks.

To verify a webhook's signature, the app must perform the following steps.

  1. Concatenate the Smile-Timestamp, followed by a period (.), followed by the webhook request body.

  2. Generate a sha256 HMAC using the computed string and the app's client secret.

  3. Compare the computed HMAC to the value of the Smile-Signature.

  4. Verify that the timestamp is within 5 minutes of the current time.

For a working example, see the code snippet below.

Ruby
Python
Ruby
require 'rubygems'
require 'base64'
require 'openssl'
require 'sinatra'
def verify_webhook_signature(body, timestamp, signature)
client_secret = ENV['SMILE_CLIENT_SECRET']
prepared_string = "#{timestamp}.#{body}"
# Compute the hmac using the client secret.
digest = OpenSSL::Digest.new('sha256')
hmac = OpenSSL::HMAC.digest(digest, client_secret, prepared_string)
encoded_hmac = Base64.encode64(hmac).strip
# Verify that the webhook has been sent within the last 5 minutes.
timestamp_plus_five_minutes = timestamp.to_i + 5 * 60
webhook_is_fresh = timestamp_plus_five_minutes > Time.now.to_i
# Check that the signature matches and the webhook is fresh.
(encoded_hmac == signature) && webhook_is_fresh
end
post '/' do
body = request.body.read
timestamp = request.env['HTTP_SMILE_TIMESTAMP']
signature = request.env['HTTP_SMILE_SIGNATURE']
# Verify the webhook signature.
verified = verify_webhook_signature(body, timestamp, signature)
puts("Webhook verified: #{verified}")
status 200
end
Python
import hmac
from base64 import b64encode
from hashlib import sha256
from os import environ
from time import gmtime
from flask import Flask, request, Response
app = Flask(__name__)
def verify_webhook_signature(body, timestamp, signature):
client_secret = environ['SMILE_CLIENT_SECRET']
prepared_string = '{timestamp}.{body}'.format(timestamp=timestamp, body=body)
# Compute the hmac using the client secret.
computed_hmac = hmac.new(client_secret, msg=prepared_string, digestmod=sha256)
digest = computed_hmac.digest()
encoded_digest = b64encode(digest)
# Verify that the webhook has been sent within the last 5 minutes.
timestamp_plus_five_minutes = int(timestamp) + 5 * 60
webhook_is_fresh = gmtime(timestamp_plus_five_minutes) > gmtime()
return (encoded_digest == signature) and webhook_is_fresh
@app.route('/', methods=['POST'])
def handler():
body = request.get_data()
timestamp = request.headers['Smile-Timestamp']
signature = request.headers['Smile-Signature']
# Verify the webhook signature.
verified = verify_webhook_signature(body, timestamp, signature)
print('Webhook verified: {verified}'.format(verified=verified))
return Response(status=200)
app.run(port=4567)

The above code snippet is functional, however in order to prevent timing attacks, a constant time string comparison function should be used when comparing signatures.

Etiquette

Webhook processing should not be synchronous. When a webhook is received, it should be added to a queuing system for asynchronous processing. This frees up Smile's server resources. Apps that take a long time to respond to webhooks may have their webhooks disabled.

When responding to a webhook, the endpoint must use a 2xx HTTP status code to indicate that the webhook was successfully received. Any status code outside of the 2xx range will be considered a delivery failure.

Retries

If a webhook fails to send for any reason, Smile will try to send the webhook again later. Smile uses an exponential back off algorithm when resending failed webhooks. If the webhook cannot be sent successfully within a 24 hour period, the webhook will be disabled for the entire app.

Permissions

OAuth permissions determine if webhooks can be sent to an app for a given Smile account.

This section discusses OAuth concepts. We suggest that you read the Smile OAuth Reference before diving into this section.

Every webhook topic has corresponding OAuth permissions. For example, in order to receive customer/updated webhooks, the app must be granted the customer:read permission.

It is important to understand that app permissions are granted on a per-account basis. This means that an app is not guaranteed to have the same scope on all of the Smile accounts that it is installed on. As a result, an app will only receive webhooks for the subset of Smile accounts that have granted it one of the required permissions.

Once a webhook is created via the Smile Partner Dashboard and at least one user has authorized the app with one of the required permissions, the app will start to receive webhooks.