HTTP Destination
This repository contains documentation and samples for building publish destinations using the "generic" HTTP destination built into Kapost.
Contact your customer success manager to learn more.
Getting Started
The HTTP destination that is built into Kapost allows building custom publish destinations by the means of hitting a user built custom HTTP (REST) endpoint.
This is not a 100% traditional endpoint and in a sense it is more similar to an endpoint that would receive and handle an incoming webhook event.
Configuration
When setting up an HTTP destination in the Kapost App Center, one has the ability to configure a number of options.
Authentication
There are four options when it comes to authentication.
Authentication | Description |
---|---|
None | No authentication |
Basic Auth | Basic authentication with username and password |
Bearer (API Key) | API Key based authentication |
Payload (JSON) | User defined custom JSON authentication payload |
The Basic Auth and Bearer (API Key) authentication types should be self explanatory.
As for the Payload (JSON), well like its name suggests, it allows you to define a custom "JSON" payload with your own key / value pairs which is then sent over with every request.
Signature
The signature options allow you to configure the signature type as well as a signature secret to be used when "computing" the signature.
Signature | Description |
---|---|
None | No signature |
SHA1 | Compute SHA1 signature |
SHA256 | Compute SHA256 signature |
Secret | Secret to compute the SHA1 or SHA256 |
Custom Header
The custom header allows you to configure a custom header that is sent along with all requests made to the defined endpoint.
The header name must be in the format "X-Your-Header-Name". Configuring a custom header is optional and should only be used when there's an actual need for sending along some additional metadata with every request.
Endpoint
The endpoint lets you define a canonical HTTP endpoint that the HTTP destination will interact with and make requests to.
Endpoint | Description |
---|---|
URL | Canonical HTTP endpoint URL |
The provided samples assume that you configured the HTTP destination in the Kapost App Center with Bearer (API Key) authentication and a SHA256 signature.
Actions
At this point in time, any HTTP endpoint defined in the App Center is expected to implement three distinct "actions" for a successful connection and integration.
All actions come in the form of a "POST" request to the defined HTTP endpoint.
In addition, regardless of the authentication and signature configuration, all requests made to the defined HTTP endpoint will have the following mandatory request headers.
Name | Value |
---|---|
Content-Type | application/json |
X-Kapost-Platform | HTTP |
X-Kapost-Action | auth, publish or republish |
When "Basic Auth" has been configured as the preferred authentication method then there will be an additional header.
Name | Value |
---|---|
Authorization | Basic base64_encoded_username_password |
If you are an unsure of what this means in practice, please click here in order to learn more.
When "Bearer (API Key)" has been configured as the preferred authentication method then there will be an additional header.
Name | Value |
---|---|
Authorization | Bearer api_key |
If you are an unsure of what this means in practice, please click here in order to learn more.
When "SHA1" or "SHA256" has been configured as the preferred signature method, then there will be an additional header.
Name | Value |
---|---|
X-Kapost-Signature | sha1 or sha256 signature (i.e sha256=xyz) |
Responses
All responses returned by the defined HTTP endpoint must set the Content-Type
header to application/json
.
Errors
In the event of any errors during any of the "actions", the HTTP status code must be set and a well formed JSON response must be returned.
For instance, in the case of an authentication error, the HTTP status code must be set to 401, and the returned response must look like the one presented below.
{
"error": "Authentication error. Invalid credentials."
}
All error messages will be shown to users, therefore try to make them concise and to the point, without cryptic error codes or magic numbers.
If Kapost fails to "extract" the error from the response, it will simply show "Unexpected error" to the users.
Signature
When "SHA1" or "SHA256" has been configured as the preferred signature method,
then you must compute the signature and check it against the value of the
X-Kapost-Signature
header.
computed_signature = [
"sha256",
OpenSSL::HMAC.hexdigest(
OpenSSL::Digest.new("sha256"),
ENV.fetch("SIGNATURE_SECRET"),
[
request.headers["X-Kapost-Platform"],
request.headers["X-Kapost-Action"],
request.body.read
].join
)
].join("=")
unless Rack::Utils.secure_compare?(headers["X-Kapost-Signature"], computed_signature)
raise_error(403, "Invalid signature")
end
In order to avoid "timing" based attacks, one should never compare the computed signatures with the good old regular equality operator provided by the language of choice, but rather use a "secure compare" method provided by the crypto library.
Please consult the provided samples for concrete implementations of signature verification by clicking here.
Authentication
The "auth" action is called when the connection is made in the Kapost App Center and it is used to verify the credentials as well as discover the supported capabilities of the endpoint.
Request Headers
Name | Value |
---|---|
Content-Type | application/json |
X-Kapost-Platform | HTTP |
X-Kapost-Action | auth |
Request Payload
{
"authentication": {
"instance": {
"id": "5c536647480a0e68ee000001",
"subdomain": "instance"
},
"destination": {
"id": "5f9a8f6b480a0e23f5b0d456",
"platform": "HTTP"
}
}
}
If Payload (JSON) was configured as the preferred authentication method and the defined payload looks like the one shown below.
{
"client_id": "xxx",
"client_secret": "yyy"
}
Then the payload would take the following form.
{
"authentication": {
"client_id": "xxx",
"client_secret": "yyy",
"instance": {
"id": "5c536647480a0e68ee000001",
"subdomain": "instance"
},
"destination": {
"id": "5f9a8f6b480a0e23f5b0d456",
"platform": "HTTP"
}
}
}
Response
The "auth" action must return a "hash" of supported capabilities by the endpoint.
{
"capabilities": {
"html": true
}
}
Capability | Description |
---|---|
any_file | Available as a primary destination for Content Types with an Any File body type |
document | Available as a primary destination for Content Types with a Document body type |
html | Available as a primary destination for Content Types with an HTML body type |
social_media | Available as a primary destination for Content Types with a Social Media body type |
social_media_external_schedule | Enables support for external scheduling for Content Types with a Social Media body type. Learn more below. |
locations | Enables support for locations. Learn more below. |
draft | Enables support for publishing as draft. Learn more below. |
tracking_url | Enables support for auto-generated Bit.ly tracking URL. Learn more below. |
If social_media_external_schedule
is enabled then it is assumed that the endpoint implements
external scheduling for content types with a social media body type.
If draft
is enabled then users will have the ability to publish/republish as draft
as well as publish/republish as live.
If tracking_url
is enabled then Kapost will auto-generate a Bit.ly tracking URL
using the returned published_url
. If the published url
changes between
republishes, then the tracking url
will be regenerated.
If locations
are enabled as a supported capability, then the response is
expected to also contain an array
of locations. These locations are then
available for the user to pick from during the publishing process. This is ideal
for building an endpoint that allows to user to publish to let's say several
blogs or in conjunction with social media, allowing them to publish
to several social media destinations based on the social media channel of the
content they are working on.
{
"capabilities": {
"html": true,
"locations": true
},
"locations": [
{
"id": "uniqueid",
"name": "My Location"
},
{
"id": "uniqueid2",
"name": "My Second Location"
}
]
}
Each location must have at least an id
and name
. They can also be "grouped"
by returning a type
attribute for each.
In addition, when used in conjunction with the social media body type, they must
have a channel
attribute, indicating which social media channel they support.
Channel |
---|
YouTube |
The response can also return a "hash" of user defined metadata
. This metadata
is then included in the authentication
hash of the payloads of other actions.
{
"authentication": {
"metadata": {
"uniqueid": "dc42"
}
}
}
Publish
The publish action is called when a user publishes a piece of content that hasn't been previously published.
Request Headers
Name | Value |
---|---|
Content-Type | application/json |
X-Kapost-Platform | HTTP |
X-Kapost-Action | publish |
Request Payload
{
"action": {
"draft": true,
"scheduled_date": "2023-03-07 18:11:00 UTC"
},
"metadata": {
},
"content": {
"id": "5fa13129480a0e3ca3db3a4f",
"title": "test",
"body": "<p>test</p>",
"progress_stage":"planned",
"updated_at": "2020-11-03T10:30:52Z",
"created_at": "2020-11-03T10:30:01Z",
"file": {
"url": "https://asseturl/asset.pdf",
"file_name": "asset.pdf",
"file_size": 1024,
"file_type": "application/pdf"
},
"attachments": [
{
"url": "https://asseturl/attachment.pdf",
"file_name": "attachment.pdf",
"file_size": 1024,
"file_type": "application/pdf"
}
],
"media": [
{
"url": "https://asseturl/image.png",
"file_name": "image.png",
"file_size": 1024,
"file_type": "image/png"
},
{
"url": "https://asseturl/video.mp4",
"file_name": "video.mp4",
"file_size": 1024,
"file_type": "video/mp4"
}
],
"custom_fields": {
"hello_world_select": [
"43",
"World"
]
},
"type": {
"id": "5f7c6139480a0ec80dc16d7e",
"name": "Test Content Type",
"field_name": "test_content_type"
},
"idea": {
"id": "5c536610480a0c68ee000000"
},
"initiatives": [
{
"id": "5d536610480a0e68ee000000",
"title": "My Initiative"
}
],
"author": {
"id": "5c536610480a0e68ee000000",
"name": "user",
"email": "user@example.com"
},
"creator": {
"id": "5c536610480a0e68ee000000",
"name": "user",
"email": "user@example.com"
},
"last_updated_by": {
"id": "5c536610480a0e68ee000000",
"name": "user",
"email": "user@example.com"
}
},
"authentication": {
"metadata": {
},
"instance": {
"id": "5c536647480a0e68ee000001",
"subdomain": "instance"
},
"destination": {
"id": "5f7c43e5480a0e75849d8bf0",
"platform": "HTTP"
}
}
}
If the content that is being published has custom fields, then these custom fields will be included in the payload keyed by their user defined custom field name.
Custom field mappings can be used to customize this field name as well as map the values of dropdown (select) and multi-select custom fields on a per destination basis.
If locations are supported, then there will be a location
hash in the payload
that will look like the one presented below. This is the location
the user has picked
during the publishing process.
{
"location": {
"id": "locationid",
"name": "My Location"
}
}
If publishing as draft is supported, then the action
hash of the payload will
also contain a draft
attribute, indicating whether the user has published as draft
or not.
{
"action": {
"draft": true
}
}
If social_media_external_schedule
is supported, then the action
hash of the payload will
also contain a scheduled_date
attribute if the user decided to do a scheduled publish.
The date is in UTC.
If the user decided not to schedule, then there will be no scheduled_date
attribute in the payload.
{
"action": {
"draft": true,
"scheduled_date": "2023-03-07 18:11:00 UTC"
}
}
Response
The response must contain at least an external_id
and published_url
attribute.
Failing to return these mandatory attributes, will result in an Unexpected error
.
{
"metadata": {
"external_id": "abc33",
"published_url": "https://domain.com/content"
}
}
The external_id
attribute should be returned as a string
.
In addition, an embed_code
attribute can also be returned. This embed code
is then displayed to the user together with the published url.
Any additional attributes included in the metadata
will be persisted and
sent back in the request payload during the republish action.
Republish
The republish action is called when a user republishes a previously published
piece of content. It has the exact same payload and response as the publish
action
with one crucial difference. The external_id
and any other previously returned metadata
during the initial publish
action are also included in the payload.
Request Headers
Name | Value |
---|---|
Content-Type | application/json |
X-Kapost-Platform | HTTP |
X-Kapost-Action | republish |
Request Payload
The external_id
has special significance in Kapost, as a result it is also
included in the action
hash in addition to the metadata
.
The full content payload has been omitted for brevity.
{
"action": {
"external_id": "abc33"
},
"metadata": {
"external_id": "abc33",
"published_url": "https://domain.com/content"
},
"content": {
"id": "5fa13129480a0e3ca3db3a4f",
},
"authentication": {
"metadata": {
},
"instance": {
"id": "5c536647480a0e68ee000001",
"subdomain": "instance"
},
"destination": {
"id": "5f7c43e5480a0e75849d8bf0",
"platform": "HTTP"
}
}
}
Response
Just like in the case of the publish
action, the response must contain at least
the external_id
and published_url
attributes.
Failing to return these mandatory attributes, will result in an Unexpected error
.
{
"metadata": {
"external_id": "abc33",
"published_url": "https://domain.com/content"
}
}
Unhandled or unsupported "future" actions
New "actions" might be added in the future, therefore in order to future proof your integration, you should handle any unknown actions by simply returning an error with the HTTP status code set to 405.
{
"error": "Action is not supported by this endpoint"
}
Samples
Kapost provides sample HTTP endpoints usable by the HTTP destination in three languages, namely: Ruby, Go and PHP. Samples in other languages might be added later.
It is also possible to use automation services like Microsoft Flow, Dell Boomi, Tray and more by leveraging so called request triggers which provide an HTTP endpoint that the Kapost HTTP destination can hit. This way one doesn't have to write a single line of code, thus significantly reducing the barrier of entry for building publish integrations.
To view the samples click here.
License
For more information see LICENSE.