Webhook Forwarding

This page contains importable postman collection files which are ready to import into Postman for instant testing. This is in JSON format, please copy and save the text in JSON format before importing in Postman.

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:

VariableDescriptionExample
API_VERSIONRepresents 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
apiKeyYour API key. Never share this with anyone.
addressThe URL to send all incoming webhooks from Alloy. This should be a POST endpoint on your server.
topicThe 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.

{
	"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": [
					""
				]
			}
		}
	]
}