Skip to content

Instantly share code, notes, and snippets.

@vtrehan
Last active December 20, 2015 10:21
Show Gist options
  • Save vtrehan/9ef1e16db5e74305ddf3 to your computer and use it in GitHub Desktop.
Save vtrehan/9ef1e16db5e74305ddf3 to your computer and use it in GitHub Desktop.

Layer Webhooks Pre-release

Webhooks allow you to develop integrations which subscribe to events within your Layer application. When a subscribed event is triggered, Layer will send an HTTP POST payload to the endpoint designated in the webhook configuration. Webhooks provide a simple, flexible mechanism that you can use to signal an external system to take action in response to messaging activity within your app. You can use Webhooks to implement things like:

  • Add context to conversations: respond to messages mentioning keywords.
  • Notify another user when they are @mentioned
  • Send a welcome message when a user registers (build a great onboarding experience)
  • Integrate a third-party service (Uber API, Weather Underground, etc) into conversations based on context

Event Types

When configuring a Webhook, you choose which events you would like to receive payloads for by subscribing to the specific events you intend to handle.

Each event type corresponds to a specific action that can occur within your Layer application. The current set of available event types are:

Event Description
message.sent When a Message is sent.
message.delivered When a client acknowledges delivery of a Message.
message.read When a client marks a Message as read.
message.deleted When a client deletes a Message (Global deletion mode only).
conversation.created When a new Conversation is created.
conversation.participants_updated When a Conversation is updated participant changes.
conversation.metadata_updated When a Conversation is updated for metadata changes.
conversation.deleted When a Conversation is deleted (Global deletion mode only).

###Status

Every webhook created has three possible values: unverified, active and inactive. A newly created webhook will have the status on unverified. Please see verify the webhook section for additional details.

##WebHooks API

Authentication

Authentication for the webhooks API will be based on a token generated from within the developer dashboard (same token as the Platform API). This token can be generated with an expiry period and/or revoked.

The token needs to be included in the Authorization header of each HTTP request as follows:

Authorization: Bearer x32pYli9DCBByPUzz3CMLU9jDTYdiAacNiJrMIkdp4lTf6sb

If the token is missing or invalid, the server will respond with 401 Unauthorized.

####Create a WebHook:

Name Type Description
target_url string Destination URL for the WebHook (must be HTTPS)
event_types array of strings Types of events that will cause this WebHook to be triggered
secret string String that’s passed with the HTTP requests as an layer-webhook-signature header. The value of this header is computed as the HMAC hex digest of the body, using the secret as the key.
target_config dictionary A free form dictionary of supplemental data specific to the WebHook
POST https://api.layer.com/apps/{app_id}/webhooks
{
    "target_url": "https://client.example.com/layeruser/foo",
    "event_types": [
      "user.registered",
      "conversation.created"
    ],
	"secret": "1697f925ec7b1697f925ec7b"
	"target_config" : {
		"key1" : "value1",
		"key2" : "value2"
	}
}

> 201 Created  {
    "id": "layer:///apps/082d4684-0992-11e5-a6c0-1697f925ec7b/webhooks/f5ef2b54-0991-11e5-a6c0-1697f925ec7b",
	"url": "https://api.layer.com/082d4684-0992-11e5-a6c0-1697f925ec7b/webhooks/f5ef2b54-0991-11e5-a6c0-1697f925ec7b"
    "target_url": "https://client.example.com/layeruser/foo",
    "event_types": [
      "user.registered",
      "conversation.created"
    ],
    "status": "unverified",
    "created_at": "2015-03-14T13:37:27Z",
	"target_config" : {
		"key1" : "value1",
		"key2" : "value2"
	}
  }

#####Verify the webhook The first request to your new webhook URL will be a verification request to confirm that Layer is communicating with the correct service. This helps in avoiding your information from leaking to unintended recipients.

The verification request will be a GET request with a verification_challenge parameter, which is a random string. Your service should echo back the challenge parameter as the body of its response. Once Layer receives a valid response, the endpoint is considered to be a valid webhook, and Layer will begin sending notifications of file changes. Layer will not attempt to retry the verification request. Note that the Content-Type for the verification response will be ignored.

> GET https://client.example.com/layeruser/foo?verification_challenge=de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9

Expected Response < 200 OK de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9

####List WebHooks:

GET https://api.layer.com/apps/{app_id}/webhooks

 > 200 OK 
[
  {
    "id": "layer:///apps/082d4684-0992-11e5-a6c0-1697f925ec7b/webhooks/f5ef2b54-0991-11e5-a6c0-1697f925ec7b",
	"url": "https://api.layer.com/082d4684-0992-11e5-a6c0-1697f925ec7b/webhooks/f5ef2b54-0991-11e5-a6c0-1697f925ec7b"
    "target_url": "https://client.example.com/layeruser/foo",
    "event_types": [
      "user.registered",
      "conversation.created"
    ],
    "status": "active",
    "created_at": "2015-03-14T13:37:27Z",
	"target_config" : {
		"key1" : "value1",
		"key2" : "value2"
	}
  },
  {
    "id": "layer:///apps/082d4684-0992-11e5-a6c0-1697f925ec7b/webhooks/g6ef2b54-0991-11e5-a6c0-1697f925ec7a",
	"url": "https://api.layer.com/082d4684-0992-11e5-a6c0-1697f925ec7b/webhooks/g6ef2b54-0991-11e5-a6c0-1697f925ec7a"
    "target_url": "https://client.example.com/layeruser/foo",
    "event_types": [
      "conversation.deleted"
    ],
    "status": "inactive",
	"created_at": "2015-05-14T13:37:27Z",
	"target_config" : {}
  }
]

####Get single WebHook by ID:

GET https://api.layer.com/apps/{app_id}/webhooks/{webhook_id}

> 200 OK {
    "id": "layer:///apps/082d4684-0992-11e5-a6c0-1697f925ec7b/webhooks/f5ef2b54-0991-11e5-a6c0-1697f925ec7b",
	"url": "https://api.layer.com/082d4684-0992-11e5-a6c0-1697f925ec7b/webhooks/f5ef2b54-0991-11e5-a6c0-1697f925ec7b"
    "target_url": "https://client.example.com/layeruser/foo",
    "event_types": [
      "user.registered",
      "conversation.created"
    ],
    "status": "active",
    "created_at": "2015-03-14T13:37:27Z",
	"target_config" : {
		"key1" : "value1",
		"key2" : "value2"
	}
  }

####Activate a WebHook:

POST https://api.layer.com/apps/{app_id}/webhooks/{webhook_id}/activate

> 200 OK {
    "id": "layer:///apps/082d4684-0992-11e5-a6c0-1697f925ec7b/webhooks/f5ef2b54-0991-11e5-a6c0-1697f925ec7b",
	"url": "https://api.layer.com/082d4684-0992-11e5-a6c0-1697f925ec7b/webhooks/f5ef2b54-0991-11e5-a6c0-1697f925ec7b"
    "target_url": "https://client.example.com/layeruser/foo",
    "event_types": [
      "user.registered",
      "conversation.created"
    ],
    "status": "unverified",
    "created_at": "2015-03-14T13:37:27Z",
	"target_config" : {
		"key1" : "value1",
		"key2" : "value2"
	}
  }

This is a no-op if the webhook is already active. If the webhook is unverified or disabled this results in a webhook verification request to the target_url, and transitioned to the active state once the verification is completed.

####Deactivate a WebHook:

POST https://api.layer.com/apps/{app_id}/webhooks/{webhook_id}/deactivate
> 200 OK {
    "id": "layer:///apps/082d4684-0992-11e5-a6c0-1697f925ec7b/webhooks/f5ef2b54-0991-11e5-a6c0-1697f925ec7b",
	"url": "https://api.layer.com/082d4684-0992-11e5-a6c0-1697f925ec7b/webhooks/f5ef2b54-0991-11e5-a6c0-1697f925ec7b"
    "target_url": "https://client.example.com/layeruser/foo",
    "event_types": [
      "user.registered",
      "conversation.created"
    ],
    "status": "inactive",
    "created_at": "2015-03-14T13:37:27Z",
	"target_config" : {
		"key1" : "value1",
		"key2" : "value2"
	}
  }

####Delete a WebHook:

DELETE https://api.layer.com/apps/{app_id}/webhooks/{webhook_id}

> 204 (No Content)

Webhook Requests

Each event type has a specific payload with relevant information about the event. These payloads are detailed on a per-event basis in the Event Payloads section below.

Request Headers

All Webhook requests are delivered with a set of HTTP headers detailing context about the event.

Header Description
layer-webhook-event-type Name of the event that triggered delivery.
layer-webhook-signature The value of this header is computed as the HMAC hex digest of the body, using the secret config option as the key.
layer-webhook-request-id A unique ID for this Webhook request.
layer-webhook-id The ID for the webhook.
  • WebHook requests are delivered with a User-Agent of layer-webhooks/1.0.
  • The WebHook requests will have the Content-Type header set to application/json.

Validating Payload Integrity

The secret specified when a Webhook is created is used to compute a Hash-based Message Authentication Code (or HMAC) over the serialized request body before it is sent. The HMAC is delivered with the request via the layer-webhook-signature HTTP header.

When integrating a Layer Webhook with your application, it is recommended that you verify the integrity of the payload by validating the signature given in the layer-webhook-signature header. You can do so by feeding the secret and complete request body to a crypto library (such as OpenSSL) that is capable of computing an HmacSHA1 digest. If the request is valid and the body has not been tampered with, the computed signature will match the header value exactly.

Responding to a Webhook

The endpoint receiving the Layer webhook is expected to respond with a 2xx status code in order to acknowledge delivery of the request. Any non-2xx response will be considered a delivery failure and will be retried with exponential backoff for a period of time (upto 10 retries).

The retries will have the same value for the layer-webhook-request-id header, the target server should be able to handle duplicate transmissions of the webhook requests. If the webhook request fails to get a response it will be transitioned to the inactive status and will need to be reactivated using the activation API. The Web UI will display the reason when a webhook is deactivated.

Event Payloads

The remaining section details the JSON format for request bodies of each subscribable Webhook event type.

message.sent

Triggered when a new Message is sent.

{
	"event_timestamp":"2015-09-17T20:46:47.561Z",
	"event_type":"message.sent",
	"event_id":"c12f340d-3b62-4cf1-9b93-ef4d754cfe69",
	"message": {
	    "id": "layer:///messages/940de862-3c96-11e4-baad-164230d1df67",
	    "conversation": {
	    	"id": "layer:///conversations/e67b5da2-95ca-40c4-bfc5-a2a8baaeb50f",
	    	"url": "https://api.layer.com/apps/082d4684-0992-11e5-a6c0-1697f925ec7b/conversations/e67b5da2-95ca-40c4-bfc5-a2a8baaeb50f"
	    },
	    "parts": [
	        {
	        	"id": "layer:///messages/940de862-3c96-11e4-baad-164230d1df67/parts/0",
	            "mime_type": "text/plain",
	            "body": "This is the message.",
	            "size": 20
	        },
	        {
	            "mime_type": "image/png",
	            "content": {
	           	"id": "layer:///messages/940de862-3c96-11e4-baad-164230d1df67/parts/1",
	                "download_url": "http://google-testbucket.storage.googleapis.com/some/download/path",
	                "expiration": "2014-09-09T04:44:47+00:00",
	                "refresh_url": "https://api.layer.com/content/7a0aefb8-3c97-11e4-baad-164230d1df67",
	                "size": 172114124
	            },
	            "size": 172114124
	        }
	    ],
	    "sent_at": "2014-09-09T04:44:47+00:00",
	    "sender": {
			"user_id": "12345"
	    },
	    "recipient_status": {
	        "12345": "read",
	        "999": "sent",
	        "111": "sent"
	    }
	},
	"target_config" : {
		"key1" : "value1",
		"key2" : "value2"
	}
}

message.delivered

Triggered when a Message recipient acknowledges delivery of a Message.

{	
	"event_timestamp":"2015-09-17T20:46:47.561Z",
	"event_type":"message.delivered",
	"event_id":"c12f340d-3b62-4cf1-9b93-ef4d754cfe69",
	"message": {
	    "id": "layer:///messages/940de862-3c96-11e4-baad-164230d1df67",
	    "conversation": {
	    	"id": "layer:///conversations/e67b5da2-95ca-40c4-bfc5-a2a8baaeb50f",
			"url": "https://api.layer.com/apps/082d4684-0992-11e5-a6c0-1697f925ec7b/conversations/e67b5da2-95ca-40c4-bfc5-a2a8baaeb50f"
	    },
	    "parts": [
	        {
	        	"id": "layer:///messages/940de862-3c96-11e4-baad-164230d1df67/parts/0",
	            "mime_type": "text/plain",
	            "body": "This is the message.",
	            "size": 20
	        },
	        {
	            "mime_type": "image/png",
	            "content": {
	            	"id": "layer:///messages/940de862-3c96-11e4-baad-164230d1df67/parts/1",
	                "download_url": "http://google-testbucket.storage.googleapis.com/some/download/path",
	                "expiration": "2014-09-09T04:44:47+00:00",
	                "refresh_url": "https://api.layer.com/content/7a0aefb8-3c97-11e4-baad-164230d1df67",
	                "size": 172114124
	            },
	            "size": 172114124
	        }
	    ],
	    "sent_at": "2014-09-09T04:44:47+00:00",
	    "sender": {
			"name": "t-bone"
	    },
	    "recipient_status": {
	        "777": "sent",
	        "12345": "read",
	        "111": "delivered"
	    }
	},
	"target_config" : {
		"key1" : "value1",
		"key2" : "value2"
	}
}

message.read

Triggered when a Message recipient marks a Message as read.

{
	"event_timestamp":"2015-09-17T20:46:47.561Z",
	"event_type":"message.read",
	"event_id":"c12f340d-3b62-4cf1-9b93-ef4d754cfe69",
	"message": {
	    "id": "layer:///messages/940de862-3c96-11e4-baad-164230d1df67",
	    "conversation": {
	    	"id": "layer:///conversations/e67b5da2-95ca-40c4-bfc5-a2a8baaeb50f",
			"url": "https://api.layer.com/apps/082d4684-0992-11e5-a6c0-1697f925ec7b/conversations/e67b5da2-95ca-40c4-bfc5-a2a8baaeb50f"
	    },
	    "parts": [
	        {
	        	"id": "layer:///messages/940de862-3c96-11e4-baad-164230d1df67/parts/0",
	            "mime_type": "text/plain",
	            "body": "This is the message.",
	            "size": 20
	        },
	        {
            	"id": "layer:///messages/940de862-3c96-11e4-baad-164230d1df67/parts/1",
	            "mime_type": "image/png",
	            "content": {
	            	"id": "layer:///content/3d0736d9-1a50-4e9a-a9b3-2400caa9e161",
	                "download_url": "http://google-testbucket.storage.googleapis.com/some/download/path",
	                "expiration": "2014-09-09T04:44:47+00:00",
	                "size": 172114124
	            },
	        }
	    ],
	    "sent_at": "2014-09-09T04:44:47+00:00",
	    "sender": {
			"user_id": "12345"
	    },
	    "recipient_status": {
	        "12345": "read",
	        "999": "read",
	        "111": "delivered"
	    }
	},
	"target_config" : {
		"key1" : "value1",
		"key2" : "value2"
	}
}

message.deleted

Triggered when a Message is globally deleted.

{
	"event_timestamp":"2015-09-17T20:46:47.561Z",
	"event_type":"message.deleted",
	"event_id":"c12f340d-3b62-4cf1-9b93-ef4d754cfe69",
	"message": {
	    "id": "layer:///messages/940de862-3c96-11e4-baad-164230d1df67",
	    "conversation": {
	    	"id": "layer:///conversations/e67b5da2-95ca-40c4-bfc5-a2a8baaeb50f",
			"url": "https://api.layer.com/apps/082d4684-0992-11e5-a6c0-1697f925ec7b/conversations/e67b5da2-95ca-40c4-bfc5-a2a8baaeb50f"
	    },
	    "parts": [
	        {
	        	"id": "layer:///messages/940de862-3c96-11e4-baad-164230d1df67/parts/0",
	            "mime_type": "text/plain",
	            "body": "This is the message."
	        },
	        {
            	"id": "layer:///messages/940de862-3c96-11e4-baad-164230d1df67/parts/1",
	            "mime_type": "image/png",
	            "content": {
	            	"id": "layer:///content/3d0736d9-1a50-4e9a-a9b3-2400caa9e161",
	                "download_url": "http://google-testbucket.storage.googleapis.com/some/download/path",
	                "expiration": "2014-09-09T04:44:47+00:00",
	                "size": 172114124
	            }
	        },
			{
		    	"mime_type": "image/jpeg",
			    "body": "iVBORw0KGgoAAAANSUhEUgAAACA=",
			    "encoding": "base64",
			    "id": "layer:///messages/940de862-3c96-11e4-baad-164230d1df67/parts/2",
			}
	    ],
	    "sent_at": "2014-09-09T04:44:47+00:00",
	    "sender": {
			"user_id": "12345"
	    },
	    "recipient_status": {
	        "12345": "read",
	        "999": "read",
	        "111": "delivered"
	    }
	},
	"target_config" : {
		"key1" : "value1",
		"key2" : "value2"
	}
}

conversation.created

Triggered when a new Conversation is created.

{
	"event_timestamp":"2015-09-17T20:46:47.561Z",
	"event_type":"conversation.created",
	"event_id":"c12f340d-3b62-4cf1-9b93-ef4d754cfe69",
    "conversation": {
		"id": "layer:///conversations/f3cc7b32-3c92-11e4-baad-164230d1df67",
		"url": "https://api.layer.com/apps/082d4684-0992-11e5-a6c0-1697f925ec7b/conversations/e67b5da2-95ca-40c4-bfc5-a2a8baaeb50f",
	    "created_at": "2014-09-15T04:44:47+00:00",    
		"messages_url": "https://api.layer.com/conversations/c12fd916-1390-464b-850f-1380a051f7c8/messages",
	    "distinct": false,
	    "participants": [
	        "1234",
	        "5678"
	    ],
	    "metadata": {
	    	"favorite": "true",
	    	"background_color": "#3c3c3c"
	    }
	},
	"target_config" : {
		"key1" : "value1",
		"key2" : "value2"
	}
}

conversation.participants_updated

Triggered when a Conversation is updated through mutation of the participants list.

{
	"event_timestamp":"2015-09-17T20:46:47.561Z",
	"event_type":"conversation.participants_updated",
	"event_id":"c12f340d-3b62-4cf1-9b93-ef4d754cfe69",
	"conversation": {
	    "id": "layer:///conversations/f3cc7b32-3c92-11e4-baad-164230d1df67",
		"url": "https://api.layer.com/apps/082d4684-0992-11e5-a6c0-1697f925ec7b/conversations/e67b5da2-95ca-40c4-bfc5-a2a8baaeb50f",
	    "created_at": "2014-09-15T04:44:47+00:00",    
		"messages_url": "https://api.layer.com/conversations/c12fd916-1390-464b-850f-1380a051f7c8/messages",
	    "distinct": false,
	    "participants": [
	        "1234",
	        "5678"
	    ],
	    "metadata": {
	    	"favorite": "true",
	    	"background_color": "#3c3c3c"
	    }
	},
	"target_config" : {
		"key1" : "value1",
		"key2" : "value2"
	}
}

conversation.metadata_updated

Triggered when a Conversation is updated through mutation of metadata.

{
	"event_timestamp":"2015-09-17T20:46:47.561Z",
	"event_type":"conversation.metadata_updated",
	"event_id":"c12f340d-3b62-4cf1-9b93-ef4d754cfe69",
	"conversation": {
	    "id": "layer:///conversations/f3cc7b32-3c92-11e4-baad-164230d1df67",
		 "url": "https://api.layer.com/apps/082d4684-0992-11e5-a6c0-1697f925ec7b/conversations/e67b5da2-95ca-40c4-bfc5-a2a8baaeb50f",
	    "created_at": "2014-09-15T04:44:47+00:00",    
		 "messages_url": "https://api.layer.com/conversations/c12fd916-1390-464b-850f-1380a051f7c8/messages",
	    "distinct": false,
	    "participants": [
	        "1234",
	        "5678"
	    ],
	    "metadata": {
	    	"favorite": "true",
	    	"background_color": "#3c3c3c"
	    }
	},
	"target_config" : {
		"key1" : "value1",
		"key2" : "value2"
	}
}

conversation.deleted

Triggered when a Conversation is globally deleted.

{
	"event_timestamp":"2015-09-17T20:46:47.561Z",
	"event_type":"conversation.deleted",
	"event_id":"c12f340d-3b62-4cf1-9b93-ef4d754cfe69",
	"conversation": {
	    "id": "layer:///conversations/f3cc7b32-3c92-11e4-baad-164230d1df67",
		"url": "https://api.layer.com/apps/082d4684-0992-11e5-a6c0-1697f925ec7b/conversations/e67b5da2-95ca-40c4-bfc5-a2a8baaeb50f",
	    "created_at": "2014-09-15T04:44:47+00:00",    
		"messages_url": "https://api.layer.com/conversations/c12fd916-1390-464b-850f-1380a051f7c8/messages",
	    "distinct": false,
	    "participants": [
	        "1234",
	        "5678"
	    ],
	    "metadata": {
	    	"favorite": "true",
	    	"background_color": "#3c3c3c"
	    }
	},
	"target_config" : {
		"key1" : "value1",
		"key2" : "value2"
	}	
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment