Sending data to webhook triggers
Webhook triggers allow you to run a particular instance or flow of an instance by making an HTTP POST, PUT, PATCH, DELETE or GET request to the webhook's URL. This is useful if you would like an outside application to invoke an integration when something within the outside application occurs. The outside application can assemble some data, and send that data to a Prismatic webhook URL via an HTTP request.
For example, third-party software could invoke an instance with a JSON payload whenever a job in the third-party application is complete, like this:
curl 'https://hooks.prismatic.io/trigger/EXAMPLE==' \
--data '{"renderId":51266,"s3Bucket":"test-customer-renders","status":"complete"}' \
--header "Content-Type: application/json"
Note that the payload of the request is available by referencing the integration's trigger, shown in the screenshot below.
Steps can then reference data from the webhook payload through the trigger's results.
Headers are available through results.headers
and body data is available through results.body.data
:
You can use the GET verb to invoke an instance, but note that the GET verb does not allow you to send data with your request. If you need to send data with your request, use the POST, PUT, DELETE or PATCH verbs.
The GET verb was introduced because some applications send a GET request when a webhook is configured to verify that the webhook endpoint is ready to receive requests.
Adding a webhook trigger to a flow
- Low-Code
- Code-Native
When you add a flow to your integration, you are prompted to add a trigger to the flow. Select the Webhook Trigger option to add a webhook trigger to the flow.
If you would like to switch the trigger to a different type, click the three dots to the left of the trigger in the flow and select Change step action.
The generic webhook trigger is used by default if you omit an onTrigger
property from your flow
definition.
If you need to parse the payload that the trigger receives or send a custom response to the caller, you can define a custom trigger in your flow
definition.
See Code-native flow triggers.
Webhook trigger responses
By default, webhook triggers provide an HTTP code 200 ("OK") response to callers of the webhook. The response body contains an execution ID, which can be used later to get logs and step results from the Prismatic API. The response looks like this:
curl \
--data '{}' \
--header "Content-Type: application/json" \
'https://hooks.prismatic.io/trigger/EXAMPLE=='
{"executionId":"SW5zdGFuY2VFeGVjdXRpb25SZXN1bHQ6OTdiNWQxYmEtZGUyZi00ZDY4LWIyMTgtMDFlZGMwMTQxNTM5"}
- Low-Code
- Code-Native
You can customize the response by clicking the integration webhook trigger and selecting a different HTTP code, response body, or response content type:
Custom webhook responses can be defined in the onTrigger
block of your flow
definition.
Create an HttpResponse
object with a statusCode
, contentType
and body
to be returned to the caller:
import { HttpResponse, flow, util } from "@prismatic-io/spectral";
import { XMLParser } from "fast-xml-parser";
flow({
// ...
onTrigger: async (context, payload) => {
// Parse the raw XML from the webhook request
const parser = new XMLParser();
const parsedBody = parser.parse(util.types.toString(payload.rawBody.data));
// Respond to the request with a plaintext response that includes the challenge key
const response: HttpResponse = {
statusCode: 200,
contentType: "text/plain",
body: parsedBody.notification.challenge,
};
// Ensure that the payload is updated with the parsed body
return Promise.resolve({
payload: { ...payload, body: { data: parsedBody } },
response,
});
},
});
Other webhook trigger responses
You may encounter other responses to your webhook trigger request.
HTTP 303 See Other / Redirect to S3 Results Bucket
When your webhook trigger is invoked synchronously, your HTTP client sends a request and waits for the integration to finish running before closing the request. Prismatic responds to the request with an HTTP 303 redirect, redirecting your HTTP client to an object in an Amazon S3 bucket that contains the results of the last step of your integration.
Ensure that your HTTP client follows redirects (for curl
, you add a --location
flag).
$ curl 'https://hooks.prismatic.io/trigger/example==' --location -v
...
< HTTP/2 303
< content-type: application/json
< content-length: 0
< location: https://example.s3.us-east-2.amazonaws.com/example
< date: Wed, 28 Sep 2022 19:42:24 GMT
< x-amzn-requestid: 4c2a5179-89a2-4351-afe0-336df2cdef11
< access-control-allow-headers: Accept,CloudFront-Forwarded-Proto,CloudFront-Is-Desktop-Viewer,CloudFront-Is-Mobile-Viewer,CloudFront-Is-SmartTV-Viewer,CloudFront-Is-Tablet-Viewer,CloudFront-Viewer-ASN,CloudFront-Viewer-Country,Host,User-Agent,Via,X-Amz-Cf-Id,X-Amzn-Trace-Id,X-Forwarded-For,X-Forwarded-Port,X-Forwarded-Proto
< x-amz-apigw-id: ZL6A7GegCYcF5Ew=
< prismatic-executionid: SW5zdGFuY2VFeGVjdXRpb25SZXN1bHQ6NGVjMTJiNWMtODk2ZC00ZGJiLThjZDgtZWUwYzNlMDE4OTBh
< access-control-allow-methods: GET
< x-amzn-trace-id: Root=1-6334a39f-0f975a9a7bb343c768645c36;Sampled=1
< x-cache: Miss from cloudfront
< via: 1.1 d67353af1bc95b93fa6102d888271954.cloudfront.net (CloudFront)
< x-amz-cf-pop: ORD58-P7
< x-amz-cf-id: xraa6hPBPc_texazIJXdKgavfwVRMW2oV85GqwCx6xUkUck7MxGKPg==
<
...
* Connection #0 to host hooks.prismatic.io left intact
* Issue another request to this URL: 'https://example.s3.us-east-2.amazonaws.com/example'
...
< HTTP/1.1 200 OK
< x-amz-id-2: zx2ZHsGlC/Szu3HCp7xjRAVDALsEdQ/TJ5x/MkcbFkAB8DjLmRJOWCyTGmpI93UhdpqeYxt7hVo=
< x-amz-request-id: SE5KV3CJ5AXCYGZA
< Date: Wed, 28 Sep 2022 19:42:25 GMT
< Last-Modified: Wed, 28 Sep 2022 19:42:25 GMT
< x-amz-expiration: expiry-date="Sat, 29 Oct 2022 00:00:00 GMT", rule-id="expire-old-step-results"
< ETag: "083be81885a78809b54f4deead0e6c24"
< x-amz-server-side-encryption: AES256
< x-amz-version-id: 7bc_zpEQGmpc_stsaC.9vcF1DqxzXWx.
< Accept-Ranges: bytes
< Content-Type: application/octet-stream
< Server: AmazonS3
< Content-Length: 11
<
* Connection #1 to host payload-bucket20200616192411543900000009.s3.us-east-2.amazonaws.com left intact
{"item":"Widgets","quantity":5}
Combining --location
and an explicit --request POST
(or -X POST
) flag in the same curl
command can have unintended consequences.
curl
will be redirected to an S3 bucket, but will attempt to make a POST
request (rather than a GET
request) to the S3 bucket.
This results in S3 responding with a SignatureDoesNotMatch
error.
You will see the S3 error within Postman if you enable Follow original HTTP method. Keep that option unchecked.
HTTP 400 Bad Request
You'll see an HTTP 400 response for one of two reasons:
-
If your request was malformed (for example, you have a header
content-type: application/json
, but the data you sent wasn't valid JSON).Malformed payload$ curl 'https://hooks.treece.prismatic-dev.io/trigger/example==' -v \
--data "{bad-data" \
--header "content-type: application/json"
...
< HTTP/2 400
< content-type: application/json
< content-length: 44
< date: Wed, 28 Sep 2022 19:47:09 GMT
< x-amzn-requestid: 64a06065-c2d5-4d1b-8188-1513f072cb8e
< access-control-allow-headers: Accept,CloudFront-Forwarded-Proto,CloudFront-Is-Desktop-Viewer,CloudFront-Is-Mobile-Viewer,CloudFront-Is-SmartTV-Viewer,CloudFront-Is-Tablet-Viewer,CloudFront-Viewer-ASN,CloudFront-Viewer-Country,content-type,Host,User-Agent,Via,X-Amz-Cf-Id,X-Amzn-Trace-Id,X-Forwarded-For,X-Forwarded-Port,X-Forwarded-Proto
< x-amz-apigw-id: ZL6tYEjPCYcFmMA=
< prismatic-executionid: SW5zdGFuY2VFeGVjdXRpb25SZXN1bHQ6YmIwZmQwMjgtYmRlOS00NDFhLTg4ZTYtNjcwZDlkMDY2NjZm
< access-control-allow-methods: POST
< x-amzn-trace-id: Root=1-6334a4bb-5a02f67725c134b505472c11;Sampled=1
< x-cache: Error from cloudfront
< via: 1.1 ee57d6770700357db4b696b4c5250b82.cloudfront.net (CloudFront)
< x-amz-cf-pop: ORD58-P7
< x-amz-cf-id: qSGjLnZJNDjUkUGU2Wl297s6eezPSFs5UEF58H_hceHj9JokJkfB3A==
<
* Connection #0 to host hooks.treece.prismatic-dev.io left intact
{"error":"Received malformed JSON payload."} -
When your webhook trigger is invoked synchronously and stops because an error is thrown, your request will receive an HTTP 400 response with the error that was thrown.
Error thrown in synchronous executioncurl 'https://hooks.treece.prismatic-dev.io/trigger/example==' -v
...
* We are completely uploaded and fine
* Connection state changed (MAX_CONCURRENT_STREAMS == 128)!
< HTTP/2 400
< content-type: application/json
< content-length: 55
< date: Wed, 28 Sep 2022 19:53:33 GMT
< x-amzn-requestid: 8e46051e-2783-4e25-b95d-35ee323ce523
< access-control-allow-headers: Accept,CloudFront-Forwarded-Proto,CloudFront-Is-Desktop-Viewer,CloudFront-Is-Mobile-Viewer,CloudFront-Is-SmartTV-Viewer,CloudFront-Is-Tablet-Viewer,CloudFront-Viewer-ASN,CloudFront-Viewer-Country,content-type,Host,User-Agent,Via,X-Amz-Cf-Id,X-Amzn-Trace-Id,X-Forwarded-For,X-Forwarded-Port,X-Forwarded-Proto
< x-amz-apigw-id: ZL7paEZjiYcFU4A=
< prismatic-executionid: SW5zdGFuY2VFeGVjdXRpb25SZXN1bHQ6NTM5ZGE0YjItNDViMC00MDQxLTg3MTgtMzFhZDkwMDg1Y2Iw
< access-control-allow-methods: POST
< x-amzn-trace-id: Root=1-6334a63c-0d58198b7c2e26d11f4331f3;Sampled=1
< x-cache: Error from cloudfront
< via: 1.1 26c731836eb716e46fe9852a7aaeb508.cloudfront.net (CloudFront)
< x-amz-cf-pop: ORD58-P7
< x-amz-cf-id: ZKCbTC07GI90gLLyeRNmGaHub0gpULRK45_GZ7d7uGM3njGFYKaPkw==
<
* Connection #0 to host hooks.treece.prismatic-dev.io left intact
{"error":"The widget requested is not in the database"}
HTTP 429 Too Many Requests / Rate Limiting
A webhook endpoint URL can be invoked up to 50 times per second. If a request is received for an endpoint URL that has already received 50 requests in the last second, the request will receive a 429 "too many requests" response.
$ curl 'https://hooks.prismatic.io/trigger/example==' -v
...
< HTTP/2 429
< content-type: application/json
< content-length: 200
< date: Wed, 28 Sep 2022 19:36:55 GMT
< x-amzn-requestid: f53fbf8c-9ce6-4c4a-80a6-84edbc29fdeb
< x-amz-apigw-id: ZL5NtH82CYcFbFg=
< x-amzn-trace-id: Root=1-6334a257-1ca7ad23574a8c3255b24776;Sampled=1
< x-cache: Error from cloudfront
< via: 1.1 a044221a7cde0fa9b5dc69d5ceb4439a.cloudfront.net (CloudFront)
< x-amz-cf-pop: ORD58-P7
< x-amz-cf-id: BoOs_sbk7gB-t16ZCR4uVtD8NcPnmD40rGUPwgaouVe2hiHu60Rcjw==
<
* Connection #0 to host hooks.prismatic.io left intact
{"error":"Endpoint with id: example== has exceeded maximum allowed throughput of 50 requests/second. Please throttle your requests."}
Webhook endpoint configuration
Webhook triggers can be configured for in integration in one of three ways:
- Instance and Flow Specific: Each flow on each instance gets its own unique endpoint. This is the default configuration.
- Instance Specific: Each instance gets a unique endpoint, and the integration determines which flow to run based on header or payload data.
- Shared: All customers' instances of the integration share an endpoint. Data in the header or payload determines which customer and flow should run.
For information on configuring and troubleshooting webhook endpoints, see our Endpoint Configuration article.
Sending data to webhook triggers
A webhook parses data from the following sources:
- The request body - the JSON (or other) data that is sent to the webhook as an HTTP request body.
- The request headers - the HTTP headers.
- The URL path - The path to resource that follows the integration webhook URL.
- The URL parameters - The parameters that follow the
?
in a URL..
Take, for example, this curl
invocation:
curl \
'https://hooks.prismatic.io/trigger/EXAMPLE==/my/custom/path?param-one=ParamValueOne¶m-two=ParamValueTwo' \
--header "header-one: First header value" \
--header "header-two: Second header value" \
--header "Content-Type: application/json" \
--data '{"Payload Key 1":"Payload Value 1","Do Thing?":true,"quantity":123}'
- The request body -
{"Payload Key 1":"Payload Value 1","Do Thing?":true,"quantity":123}
- is parsed (if JSON) and is accessible to the integration by referencing the trigger'sresults.body.data.KEY-NAME
. Non-JSON payloads (like XML, images, etc) are accessible throughresults.rawBody
and can be parsed in subsequent steps that handle that type of data. - The request headers are accessible through the trigger's
results.headers.HEADER-NAME
. - The url path -
my/custom/path
- is accessible through the trigger'sresults.pathFragment
. You can pass that data into the built-in split string action and split on the/
character to split the URL path into an array['my','custom','path']
. - The url parameters -
?param-one=ParamValueOne¶m-two=ParamValueTwo
are parsed and accessible through the trigger'sresults.queryParameters.PARAMETER-NAME
.
Posting binary data with webhook triggers
If you have binary data (like an image or PDF) that you would like to post as part of your webhook invocation, you can pass that binary data in as part of your request.
For example, if you have an image, my-image.png
, you could invoke a test of an integration with:
curl 'https://hooks.prismatic.io/trigger/EXAMPLE==' \
--request POST \
--header 'Content-Type: image/png' \
--data-binary '@/path/to/my-image.png'
The binary file can be accessed by subsequent steps by referencing integrationTrigger.results.body.data
.
Posting multipart data with webhook triggers
It's useful to be able to post a combination of binary and text data to a Prismatic webhook.
For example, you might want to post information about a person, as well as an avatar image of the person, to be processed by an integration.
To do that, use a content type of multipart/form-data
with your webhook invocation:
curl 'https://hooks.prismatic.io/trigger/EXAMPLE==' \
--request POST \
--header "Content-Type: multipart/form-data" \
--form person='{"firstname":"Taylor","lastname":"Reece"};type=application/json' \
--form photo=@taylor.png
The first name in this example is accessible by referencing the trigger's results.body.data.person.data.firstname
, and the avatar image is accessible by referencing results.body.data.photo
:
Accessing webhook URLs in an integration
An integration is aware of its own webhook URLs, which is handy for setting up webhooks in third-party apps on deploy, or when you need one flow to call a sibling flow by webhook URL.
- Low-Code
- Code-Native
Those URLs are accessible by referencing the trigger's results.webhookUrls
object:
This comes in handy if you need to configure a third-party service to send data to your webhooks.
A common pattern is for one flow of your integration to be run when an instance is deployed using a deploy trigger.
That deploy-time flow can set up webhooks in a third-party app by referencing its trigger's results.webhookUrls
values.
Then, the third-party app will invoke the other flows of the integration when it needs to.
If you use shared endpoint configuration, the shared endpoint URL is accessible from results.invokeUrl
.
If you set up API keys for your deployed instances, you can access results.webhookApiKeys
similarly.
An instance's flow can have multiple API keys assigned to it, so each results.webhookApiKeys.MY FLOW NAME
is an array.
You will likely reference the first API key, results.webhookApiKeys.MY FLOW NAME.0
.
webhookUrls
in a code-native integration is available in the context
object passed to the onTrigger
, onInstanceDeploy
, onInstanceRemove
or onExecution
functions.
For example, if you want to establish a webhook in a third-party app when an instance is deployed, you can do so in the onInstanceDeploy
function:
const myFlow = flow({
// ...
onInstanceDeploy: async (context, params) => {
// Get the current flow's webhook URL
const flowWebhookUrl = context.webhookUrls[context.flow.name];
// Create a webhook in Acme
const { data } = await axios.post(
"https://api.acme.com/webhooks",
{
endpoint: flowWebhookUrl,
events: ["message.created", "message.updated"],
},
{
headers: {
Authorization: `Bearer ${context.configVars["My Connection"].fields.apiKey}`,
},
},
);
// Store the webhook ID in the flow's persistent state so it can be
// deleted on instance delete
return {
instanceState: { webhookId: data.id },
};
},
});