Webhook Forwarding
Webhook forwarding allows ISVs to receive near real time events when data changes in a third party application. For example, an ISV could subscribe to Alloy's webhook forwarding feature to get alerted every time a new order is placed in an end user's Shopify store.
Alloy's polling and realtime features ensure that data is always up to date. When events occur in 3rd party apps, we can forward those triggers onto you.
To create a subscripion, you'll need an address
and to specify the topics
you want to listen for. To get started, import the postman collection below.
You can find the required fields to set up the collection below:
Variable | Description | Example |
---|---|---|
API_VERSION | Represents the version of the Alloy Unified API you intend to make calls to. API versions are dated and new versions are released quarterly (in March, June, September, and December). | 2023-12 |
apiKey | Your API key. Never share this with anyone. | |
address | The URL to send all incoming webhooks from Alloy. This should be a POST endpoint on your server. | |
topic | The event(s) you want to subscribe to. You can subscribe to more than one topic. | connection/created , commerce/products |
Download the Collection
Download the full collection below.
JSON
{
"info": {
"_postman_id": "89e1b44d-4655-4f8f-a5fa-2426f4bf583f",
"name": "Webhook Forwarding – Alloy Unified API",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
"_exporter_id": "10048813",
"_collection_link": ""
},
"item": [
{
"name": "Create Subscription",
"event": [
{
"listen": "test",
"script": {
"exec": [
"tests[\"Status code is 200\"] = responseCode.code === 200;",
"",
"var contentTypeHeaderExists = responseHeaders.hasOwnProperty(\"Content-Type\");",
"tests[\"Has Content-Type\"] = contentTypeHeaderExists;",
" ",
"if (contentTypeHeaderExists) {",
" tests[\"Content-Type is application/json\"] = ",
" responseHeaders[\"Content-Type\"].has(\"application/json\");",
"}",
"",
"pm.test(\"response should be okay to process\", function () {",
" pm.response.to.have.status(200);",
" pm.response.to.not.be.error;",
" pm.response.to.not.have.jsonBody(\"error\");",
"});",
"",
"pm.test(\"Subscription is created correctly\", function () {",
" var jsonData = pm.response.json();",
" pm.expect(jsonData.subscription).to.not.be.null;",
" pm.expect(jsonData.subscription).to.have.property('subscriptionId');",
" pm.expect(jsonData.subscription).to.have.property('topic');",
" pm.expect(jsonData.subscription).to.have.property('address');",
" pm.expect(jsonData.subscription.topic.length).to.be.greaterThan(0);",
"});",
"",
"var jsonData = JSON.parse(responseBody);",
"postman.setEnvironmentVariable(\"subscriptionId\", jsonData.subscription.subscriptionId);",
"",
""
],
"type": "text/javascript"
}
}
],
"protocolProfileBehavior": {
"disabledSystemHeaders": {
"accept": true
}
},
"request": {
"auth": {
"type": "noauth"
},
"method": "POST",
"header": [
{
"key": "Accept",
"value": "application/json",
"type": "text"
},
{
"key": "Authorization",
"value": "Bearer {{apiKey}}",
"type": "text"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"address\": \"YOUR_WEBHOOK_URL_HERE\",\n \"topic\": [\n \"connection/created\",\n \"connection/deleted\",\n \"connection/authentication_failure\",\n \"commerce/customers\",\n \"commerce/orders\",\n \"commerce/products\",\n \"accounting/companyInfo\",\n \"accounting/accounts\",\n \"accounting/customers\",\n \"accounting/taxRates\",\n \"accounting/trackingCategories\",\n \"accounting/vendors\",\n \"accounting/items\",\n \"accounting/payments\",\n \"accounting/purchaseOrders\",\n \"accounting/bills\",\n \"accounting/invoices\",\n \"crm/accounts\",\n \"crm/contacts\",\n \"crm/leads\",\n \"crm/notes\",\n \"crm/opportunities\",\n \"crm/stages\",\n \"crm/tasks\",\n \"crm/users\",\n \"sync/started\",\n \"sync/completed\",\n \"sync/failed\"\n ]\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "https://embedded.runalloy.com/{{API_VERSION}}/one/webhooks",
"host": ["https://embedded.runalloy.com"],
"path": ["{{API_VERSION}}", "one", "webhooks"]
}
},
"response": []
},
{
"name": "Retrieve Subscription",
"event": [
{
"listen": "test",
"script": {
"exec": [
"tests[\"Status code is 200\"] = responseCode.code === 200;",
"",
"var contentTypeHeaderExists = responseHeaders.hasOwnProperty(\"Content-Type\");",
"tests[\"Has Content-Type\"] = contentTypeHeaderExists;",
" ",
"if (contentTypeHeaderExists) {",
" tests[\"Content-Type is application/json\"] = ",
" responseHeaders[\"Content-Type\"].has(\"application/json\");",
"}",
"",
"pm.test(\"response should be okay to process\", function () {",
" pm.response.to.have.status(200);",
" pm.response.to.not.be.error;",
" pm.response.to.not.have.jsonBody(\"error\");",
"});",
"",
"pm.test(\"Subscription is retrieved correctly\", function () {",
" var jsonData = pm.response.json();",
" pm.expect(jsonData.subscription).to.not.be.null;",
" pm.expect(jsonData.subscription).to.have.property('subscriptionId');",
" pm.expect(jsonData.subscription).to.have.property('topic');",
" pm.expect(jsonData.subscription).to.have.property('address');",
" pm.expect(jsonData.subscription.topic.length).to.be.greaterThan(0);",
"});",
""
],
"type": "text/javascript"
}
}
],
"protocolProfileBehavior": {
"disabledSystemHeaders": {
"accept": true
},
"disableBodyPruning": true
},
"request": {
"method": "GET",
"header": [
{
"key": "Accept",
"value": "application/json",
"type": "text"
},
{
"warning": "This is a duplicate header and will be overridden by the Authorization header generated by Postman.",
"key": "Authorization",
"value": "Bearer {{apiKey}}",
"type": "text"
}
],
"body": {
"mode": "raw",
"raw": "",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "https://embedded.runalloy.com/{{API_VERSION}}/one/webhooks/{{subscriptionId}}",
"host": ["https://embedded.runalloy.com"],
"path": ["{{API_VERSION}}", "one", "webhooks", "{{subscriptionId}}"]
}
},
"response": []
},
{
"name": "List Subscriptions",
"event": [
{
"listen": "test",
"script": {
"exec": [
"tests[\"Status code is 200\"] = responseCode.code === 200;",
"",
"var contentTypeHeaderExists = responseHeaders.hasOwnProperty(\"Content-Type\");",
"tests[\"Has Content-Type\"] = contentTypeHeaderExists;",
" ",
"if (contentTypeHeaderExists) {",
" tests[\"Content-Type is application/json\"] = ",
" responseHeaders[\"Content-Type\"].has(\"application/json\");",
"}",
"",
"pm.test(\"response should be okay to process\", function () {",
" pm.response.to.have.status(200);",
" pm.response.to.not.be.error;",
" pm.response.to.not.have.jsonBody(\"error\");",
"});",
"",
"pm.test(\"Subscription is retrieved correctly\", function () {",
" var jsonData = pm.response.json();",
" pm.expect(jsonData.subscriptions).to.not.be.null;",
" pm.expect(jsonData.subscriptions[0]).to.have.property('subscriptionId');",
" pm.expect(jsonData.subscriptions[0]).to.have.property('topic');",
" pm.expect(jsonData.subscriptions[0]).to.have.property('address');",
" pm.expect(jsonData.subscriptions[0].topic.length).to.be.greaterThan(0);",
"});",
""
],
"type": "text/javascript"
}
}
],
"protocolProfileBehavior": {
"disabledSystemHeaders": {
"accept": true
},
"disableBodyPruning": true
},
"request": {
"method": "GET",
"header": [
{
"key": "Authorization",
"value": "Bearer {{apiKey}}",
"type": "text"
},
{
"key": "Accept",
"value": "application/json",
"type": "text"
}
],
"body": {
"mode": "raw",
"raw": "",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "https://embedded.runalloy.com/{{API_VERSION}}/one/webhooks",
"host": ["https://embedded.runalloy.com"],
"path": ["{{API_VERSION}}", "one", "webhooks"]
}
},
"response": []
},
{
"name": "Delete Subscription",
"event": [
{
"listen": "test",
"script": {
"exec": [
"tests[\"Status code is 200\"] = responseCode.code === 200;",
"",
"var contentTypeHeaderExists = responseHeaders.hasOwnProperty(\"Content-Type\");",
"tests[\"Has Content-Type\"] = contentTypeHeaderExists;",
" ",
"if (contentTypeHeaderExists) {",
" tests[\"Content-Type is application/json\"] = ",
" responseHeaders[\"Content-Type\"].has(\"application/json\");",
"}",
"",
"pm.test(\"response should be okay to process\", function () {",
" pm.response.to.have.status(200);",
" pm.response.to.not.be.error;",
" pm.response.to.not.have.jsonBody(\"error\");",
"});",
"",
"pm.test(\"Subscription was deleted correctly\", function () {",
" var jsonData = pm.response.json();",
" pm.expect(jsonData).to.have.property('message');",
"});"
],
"type": "text/javascript"
}
}
],
"protocolProfileBehavior": {
"disabledSystemHeaders": {
"accept": true
}
},
"request": {
"method": "DELETE",
"header": [
{
"key": "Authorization",
"value": "Bearer {{apiKey}}",
"type": "text"
},
{
"key": "Accept",
"value": "application/json",
"type": "text"
}
],
"body": {
"mode": "raw",
"raw": "",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "https://embedded.runalloy.com/{{API_VERSION}}/one/webhooks/{{subscriptionId}}",
"host": ["https://embedded.runalloy.com"],
"path": ["{{API_VERSION}}", "one", "webhooks", "{{subscriptionId}}"]
}
},
"response": []
}
],
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "{{API_KEY}}",
"type": "string"
}
]
},
"event": [
{
"listen": "prerequest",
"script": {
"type": "text/javascript",
"exec": [""]
}
},
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [""]
}
}
]
}