diff --git a/.github/workflows/projects.json b/.github/workflows/projects.json
index d5fe1a49..4c3c0e31 100644
--- a/.github/workflows/projects.json
+++ b/.github/workflows/projects.json
@@ -34,5 +34,8 @@
},
{
"path": "integrator-default-profile/samples/github-issue-to-google-chat"
+ },
+ {
+ "path": "integrator-default-profile/samples/shopify-inventory-alert-via-twilio-sms"
}
]
diff --git a/integrator-default-profile/samples/shopify-inventory-alert-via-twilio-sms/.choreo/component.yaml b/integrator-default-profile/samples/shopify-inventory-alert-via-twilio-sms/.choreo/component.yaml
new file mode 100644
index 00000000..b421546a
--- /dev/null
+++ b/integrator-default-profile/samples/shopify-inventory-alert-via-twilio-sms/.choreo/component.yaml
@@ -0,0 +1,24 @@
+# +required The configuration file schema version
+schemaVersion: 1.2
+
+# +optional Incoming connection details for the component
+endpoints:
+ # +required Unique name for the endpoint.
+ # This name will be used when generating the managed API
+ - name: webhook
+ # +optional Display name for the endpoint.
+ displayName: WebHook
+ # +required Service section has the user service endpoint details
+ service:
+ # +optional Base path of the API that gets exposed via the endpoint.
+ # This is mandatory if the endpoint type is set to REST or GraphQL.
+ basePath: /
+ # +required Numeric port value that gets exposed via the endpoint
+ port: 8090
+ # +required Type of traffic that the endpoint is accepting.
+ # Allowed values: REST, GraphQL, GRPC, TCP, UDP.
+ type: REST
+ # +optional Network level visibilities of the endpoint.
+ # Accepted values: Project|Organization|Public(Default).
+ networkVisibilities:
+ - Public
diff --git a/integrator-default-profile/samples/shopify-inventory-alert-via-twilio-sms/.choreo/config-schema.json b/integrator-default-profile/samples/shopify-inventory-alert-via-twilio-sms/.choreo/config-schema.json
new file mode 100644
index 00000000..cd388d28
--- /dev/null
+++ b/integrator-default-profile/samples/shopify-inventory-alert-via-twilio-sms/.choreo/config-schema.json
@@ -0,0 +1,238 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "type": "object",
+ "properties": {
+ "ballerina": {
+ "type": "object",
+ "properties": {
+ "log": {
+ "type": "object",
+ "properties": {
+ "destinations": {
+ "type": "array",
+ "items": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "enum": [
+ "stderr",
+ "stdout"
+ ]
+ }
+ },
+ "additionalProperties": false,
+ "required": [
+ "type"
+ ],
+ "name": "ballerina/log:2.17.0:StandardDestination"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "enum": [
+ "file"
+ ]
+ },
+ "path": {
+ "type": "string"
+ },
+ "mode": {
+ "enum": [
+ "APPEND",
+ "TRUNCATE"
+ ]
+ },
+ "rotation": {
+ "type": "object",
+ "properties": {
+ "policy": {
+ "enum": [
+ "BOTH",
+ "TIME_BASED",
+ "SIZE_BASED"
+ ]
+ },
+ "maxFileSize": {
+ "type": "integer"
+ },
+ "maxAge": {
+ "type": "integer"
+ },
+ "maxBackupFiles": {
+ "type": "integer"
+ }
+ },
+ "additionalProperties": false,
+ "required": [
+ "policy",
+ "maxFileSize",
+ "maxAge",
+ "maxBackupFiles"
+ ],
+ "name": "ballerina/log:2.17.0:RotationConfig"
+ }
+ },
+ "additionalProperties": false,
+ "required": [
+ "type",
+ "path",
+ "mode"
+ ],
+ "name": "ballerina/log:2.17.0:FileOutputDestination"
+ }
+ ]
+ },
+ "description": ""
+ },
+ "modules": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "level": {
+ "enum": [
+ "WARN",
+ "INFO",
+ "ERROR",
+ "DEBUG"
+ ]
+ }
+ },
+ "additionalProperties": false,
+ "required": [
+ "name",
+ "level"
+ ],
+ "name": "ballerina/log:2.17.0:Module"
+ },
+ "description": ""
+ },
+ "keyValues": {
+ "type": "object",
+ "properties": {
+ "msg": {},
+ "message": {},
+ "time": {},
+ "level": {},
+ "error": {},
+ "stackTrace": {},
+ "module": {},
+ "icp\\.runtimeId": {}
+ },
+ "additionalProperties": false,
+ "name": "ballerina/log:2.17.0:AnydataKeyValues",
+ "description": ""
+ },
+ "format": {
+ "enum": [
+ "logfmt",
+ "json"
+ ],
+ "description": ""
+ },
+ "level": {
+ "enum": [
+ "WARN",
+ "INFO",
+ "ERROR",
+ "DEBUG"
+ ],
+ "description": ""
+ },
+ "enableSensitiveDataMasking": {
+ "type": "boolean",
+ "description": ""
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "additionalProperties": false
+ },
+ "wso2": {
+ "type": "object",
+ "properties": {
+ "shopify_inventory_alert_via_twilio_sms": {
+ "type": "object",
+ "properties": {
+ "shopifyConfig": {
+ "type": "object",
+ "properties": {
+ "storeUrl": {
+ "type": "string"
+ },
+ "accessToken": {
+ "type": "string"
+ },
+ "apiSecretKey": {
+ "type": "string"
+ }
+ },
+ "additionalProperties": false,
+ "required": [
+ "storeUrl",
+ "accessToken",
+ "apiSecretKey"
+ ],
+ "description": ""
+ },
+ "twilioConfig": {
+ "type": "object",
+ "properties": {
+ "accountSid": {
+ "type": "string"
+ },
+ "authToken": {
+ "type": "string"
+ },
+ "fromNumber": {
+ "type": "string"
+ },
+ "recipientNumbers": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ },
+ "additionalProperties": false,
+ "required": [
+ "accountSid",
+ "authToken",
+ "fromNumber",
+ "recipientNumbers"
+ ],
+ "description": ""
+ },
+ "inventoryThreshold": {
+ "type": "integer",
+ "description": ""
+ },
+ "smsTemplate": {
+ "type": "string",
+ "description": ""
+ },
+ "cooldownPeriodHours": {
+ "type": "number",
+ "description": ""
+ }
+ },
+ "additionalProperties": false,
+ "required": [
+ "shopifyConfig",
+ "twilioConfig"
+ ]
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "additionalProperties": false,
+ "requiredLevel": 3
+}
diff --git a/integrator-default-profile/samples/shopify-inventory-alert-via-twilio-sms/.choreo/diagram.md b/integrator-default-profile/samples/shopify-inventory-alert-via-twilio-sms/.choreo/diagram.md
new file mode 100644
index 00000000..f912799b
--- /dev/null
+++ b/integrator-default-profile/samples/shopify-inventory-alert-via-twilio-sms/.choreo/diagram.md
@@ -0,0 +1,12 @@
+A(["Begin"]):::startNode
+B["Receive Shopify Order Webhook"]:::processNode
+C{"Inventory Below
Threshold?"}:::decisionNode
+D{"Cooldown
Expired?"}:::decisionNode
+E["Send SMS Alert via Twilio"]:::processNode
+F(["Complete"]):::endNode
+
+A --> B --> C
+C -- Yes --> D
+C -- No --> F
+D -- Yes --> E --> F
+D -- No --> F
diff --git a/integrator-default-profile/samples/shopify-inventory-alert-via-twilio-sms/.choreo/instructions.md b/integrator-default-profile/samples/shopify-inventory-alert-via-twilio-sms/.choreo/instructions.md
new file mode 100644
index 00000000..d65d93b8
--- /dev/null
+++ b/integrator-default-profile/samples/shopify-inventory-alert-via-twilio-sms/.choreo/instructions.md
@@ -0,0 +1,62 @@
+## What It Does
+
+- Receives a real-time webhook from Shopify whenever a new order is created
+- Checks the current inventory level of each product variant in the order against a configurable threshold
+- Sends an SMS alert via Twilio to one or more recipient numbers when a product's inventory drops below the configured threshold
+- Suppresses repeat alerts for the same SKU until a configurable cooldown period expires
+- Supports a customisable SMS message template with product and inventory placeholders
+
+
+
+Shopify Setup Guide
+
+1. Log in to your Shopify Admin. Your store URL follows the pattern `https://.myshopify.com`. Set this as `storeUrl`.
+2. Go to **Settings** > **Apps and sales channels** > **Develop apps** and create an app with `read_products` and `read_inventory` API scopes. Install the app and copy the **Admin API access token** — this is your `accessToken`.
+3. Log in to your Shopify account and navigate to **Settings** > **Notifications**.
+4. Click on the **Webhooks** section.
+5. Copy the key shown under _"Your webhooks will be signed with ..."_. This should be the `apiSecretKey` configuration.
+
+The following should be done after deploying the integration, and the endpoint URL is available.
+
+1. Log in to your Shopify account and navigate to **Settings** > **Notifications**.
+2. Click on the **Webhooks** section and click on **Create webhook**.
+3. In the **Create webhook** form, select the following options:
+ - **Event**: Select **Order creation** from the dropdown menu.
+ - **Format**: Choose **JSON** as the format for the webhook payload.
+ - **URL**: Enter the deployed integration's endpoint URL.
+4. Go back to the Integration Overview page, and click on **Configure Security**. Disable **OAuth2** and click on **Apply**.
+
+
+
+
+
+Twilio Setup Guide
+
+1. Create a Twilio account and obtain a phone number
+2. Obtain the Account SID and Auth Token from the Twilio Console
+3. (Optional) Enable Geographic Permissions if sending to international numbers
+
+Refer to the [Twilio documentation](https://www.twilio.com/docs/messaging/quickstart) for steps for obtaining credentials.
+
+
+
+
+
+Additional Configurations
+
+1. `inventoryThreshold`
+ - An alert is sent when an ordered product's inventory falls below this number (default: `10`)
+2. `recipientNumbers`
+ - Array of recipient phone numbers in E.164 format (e.g., `["+94711234567"]`)
+3. `cooldownPeriodHours`
+ - Minimum hours between repeat alerts for the same SKU (default: `24`)
+4. `smsTemplate`
+ - Customisable SMS message template. Available placeholders:
+ - `{{product.id}}` — Shopify product ID
+ - `{{product.name}}` — Product name
+ - `{{product.inventory}}` — Current inventory count
+ - `{{product.sku}}` — Product variant SKU
+ - `{{threshold}}` — Configured inventory threshold
+ - Default: `INVENTORY ALERT: {{product.name}} (ID: {{product.id}}) is low on stock. Current inventory: {{product.inventory}}. SKU: {{product.sku}}. Threshold: {{threshold}}`
+
+
diff --git a/integrator-default-profile/samples/shopify-inventory-alert-via-twilio-sms/.gitignore b/integrator-default-profile/samples/shopify-inventory-alert-via-twilio-sms/.gitignore
new file mode 100644
index 00000000..8e484521
--- /dev/null
+++ b/integrator-default-profile/samples/shopify-inventory-alert-via-twilio-sms/.gitignore
@@ -0,0 +1,13 @@
+# Ballerina generates this directory during the compilation of a package.
+# It contains compiler-generated artifacts and the final executable if this is an application package.
+target/
+
+# Ballerina maintains the compiler-generated source code here.
+# Remove this if you want to commit generated sources.
+generated/
+
+# Contains configuration values used during development time.
+# See https://ballerina.io/learn/provide-values-to-configurable-variables/ for more details.
+Config.toml
+config.toml
+Dependencies.toml
diff --git a/integrator-default-profile/samples/shopify-inventory-alert-via-twilio-sms/Ballerina.toml b/integrator-default-profile/samples/shopify-inventory-alert-via-twilio-sms/Ballerina.toml
new file mode 100644
index 00000000..dfb42ce7
--- /dev/null
+++ b/integrator-default-profile/samples/shopify-inventory-alert-via-twilio-sms/Ballerina.toml
@@ -0,0 +1,9 @@
+[package]
+org = "wso2"
+name = "shopify_inventory_alert_via_twilio_sms"
+version = "0.1.0"
+title = "shopify-inventory-alert-via-twilio-sms"
+distribution = "2201.13.1"
+
+[build-options]
+sticky = false
diff --git a/integrator-default-profile/samples/shopify-inventory-alert-via-twilio-sms/README.md b/integrator-default-profile/samples/shopify-inventory-alert-via-twilio-sms/README.md
new file mode 100644
index 00000000..f77991db
--- /dev/null
+++ b/integrator-default-profile/samples/shopify-inventory-alert-via-twilio-sms/README.md
@@ -0,0 +1,89 @@
+# Shopify Inventory Alert via Twilio SMS
+
+## Description
+
+This integration listens for new orders on your Shopify store and automatically sends SMS notifications via Twilio when an ordered product's inventory falls below a defined threshold.
+
+### What It Does
+
+- Receives a real-time webhook from Shopify whenever a new order is created
+- Checks the current inventory level of each product variant in the order against a configurable threshold
+- Sends SMS alerts to one or more recipient phone numbers when stock is low
+- Enforces a per-SKU cooldown period to prevent duplicate alerts within a configured time window
+- Supports a fully customizable SMS message template with product-specific placeholders
+
+## Prerequisites
+
+Before running this integration, you need:
+
+### Shopify Setup
+
+- A Shopify store with Admin API access
+- A Shopify Admin API access token with the following scopes: `read_products`, `read_inventory`
+- Your store's permanent `.myshopify.com` domain
+- To create a new app and obtain an access token, follow the [Shopify Admin API getting started guide](https://shopify.dev/docs/api/admin-rest)
+- A webhook configured in **Shopify Admin → Settings → Notifications → Webhooks**:
+ - Copy the signing secret shown under _"Your webhooks will be signed with"_ — this is your `apiSecretKey`
+ - Click **Create webhook**, set **Event** to `Order creation`, **Format** to `JSON`, and set the **URL** to the public endpoint of this deployed integration
+
+### Twilio Setup
+
+- A Twilio account with an active SMS-capable phone number
+- The recipient country must be enabled under **Twilio Console → Messaging → Settings → Geo Permissions**
+- For trial accounts, recipient numbers must be verified under **Twilio Console → Phone Numbers → Verified Caller IDs**
+
+## Configuration
+
+The following configurations are required to connect to Shopify and Twilio.
+
+### Shopify Credentials
+
+- `storeUrl` - Your store URL (e.g., `https://your-store.myshopify.com`)
+- `accessToken` - Admin API access token (`shpat_...`)
+- `apiSecretKey` - Webhook signing secret from **Shopify Admin → Settings → Notifications → Webhooks**
+
+### Twilio Credentials
+
+- `accountSid` - Your Twilio Account SID (`AC...`)
+- `authToken` - Your Twilio Auth Token
+- `fromNumber` - Your Twilio phone number in E.164 format (e.g., `+12025551234`)
+- `recipientNumbers` - One or more recipient phone numbers in E.164 format
+
+### Inventory Monitoring
+
+- `inventoryThreshold` - Minimum stock level that triggers an alert (default: `10`)
+- `cooldownPeriodHours` - Minimum hours before re-alerting on the same SKU (default: `24`)
+
+### Notification Settings
+
+- `smsTemplate` - Customizable SMS message using the placeholders below
+
+#### SMS Template Placeholders
+
+- `{{product.id}}` - Shopify product ID
+- `{{product.name}}` - Display name of the product
+- `{{product.inventory}}` - Current stock quantity
+- `{{product.sku}}` - Product variant SKU
+- `{{threshold}}` - Configured inventory threshold
+
+**Default template:**
+```text
+INVENTORY ALERT: {{product.name}} (ID: {{product.id}}) is low on stock. Current inventory: {{product.inventory}}. SKU: {{product.sku}}. Threshold: {{threshold}}
+```
+
+**Example output:**
+```text
+INVENTORY ALERT: Blue Denim Jacket (ID: 8194105999407) is low on stock. Current inventory: 3. SKU: BDJ-001. Threshold: 10
+```
+
+## Deploying on **Devant**
+
+1. Sign in to your Devant account.
+2. Create a new Integration and follow the instructions in [Devant Documentation](https://wso2.com/devant/docs/references/import-a-repository/) to import this repository.
+3. Select the **Technology** as `WSO2 Integrator: BI`.
+4. Choose the **Integration Type** as `Trigger` and click **Create**.
+5. Once the build is successful, click **Configure to Continue** and set up the required environment variables for Shopify and Twilio credentials.
+6. Click **Deploy** to start the integration.
+7. Copy the public endpoint URL of the deployed integration.
+8. In **Shopify Admin → Settings → Notifications → Webhooks**, click **Create webhook**, set **Event** to `Order creation`, **Format** to `JSON`, and paste the endpoint URL.
+9. Once tested, promote the integration to production. Make sure to set the relevant environment variables in the production environment as well.
diff --git a/integrator-default-profile/samples/shopify-inventory-alert-via-twilio-sms/agents.bal b/integrator-default-profile/samples/shopify-inventory-alert-via-twilio-sms/agents.bal
new file mode 100644
index 00000000..e69de29b
diff --git a/integrator-default-profile/samples/shopify-inventory-alert-via-twilio-sms/automation.bal b/integrator-default-profile/samples/shopify-inventory-alert-via-twilio-sms/automation.bal
new file mode 100644
index 00000000..e69de29b
diff --git a/integrator-default-profile/samples/shopify-inventory-alert-via-twilio-sms/config.bal b/integrator-default-profile/samples/shopify-inventory-alert-via-twilio-sms/config.bal
new file mode 100644
index 00000000..94ab72c6
--- /dev/null
+++ b/integrator-default-profile/samples/shopify-inventory-alert-via-twilio-sms/config.bal
@@ -0,0 +1,21 @@
+configurable record {
+ string storeUrl;
+ string accessToken;
+ string apiSecretKey;
+} shopifyConfig = ?;
+
+configurable record {
+ string accountSid;
+ string authToken;
+ string fromNumber;
+ string[] recipientNumbers;
+} twilioConfig = ?;
+
+// Inventory threshold configuration
+configurable int inventoryThreshold = 10;
+
+// SMS template with placeholders: {{product.name}}, {{product.id}}, {{product.inventory}}, {{product.sku}}, {{threshold}}
+configurable string smsTemplate = "INVENTORY ALERT: {{product.name}} (ID: {{product.id}}) is low on stock. Current inventory: {{product.inventory}}. SKU: {{product.sku}}. Threshold: {{threshold}}";
+
+// Cooldown period in hours (don't re-alert same SKU within X hours)
+configurable decimal cooldownPeriodHours = 24.0;
diff --git a/integrator-default-profile/samples/shopify-inventory-alert-via-twilio-sms/connections.bal b/integrator-default-profile/samples/shopify-inventory-alert-via-twilio-sms/connections.bal
new file mode 100644
index 00000000..c87f6c7b
--- /dev/null
+++ b/integrator-default-profile/samples/shopify-inventory-alert-via-twilio-sms/connections.bal
@@ -0,0 +1,22 @@
+import ballerinax/trigger.shopify;
+import ballerinax/shopify.admin;
+import ballerinax/twilio;
+
+shopify:ListenerConfig listenerConfig = {
+ apiSecretKey: shopifyConfig.apiSecretKey
+};
+
+listener shopify:Listener shopifyListener = new (listenerConfig, 8090);
+
+// Twilio client
+final twilio:Client twilioClient = check new ({
+ auth: {
+ accountSid: twilioConfig.accountSid,
+ authToken: twilioConfig.authToken
+ }
+});
+
+// Shopify admin client (used to fetch current inventory levels for ordered products)
+final admin:Client adminClient = check new ({
+ xShopifyAccessToken: shopifyConfig.accessToken
+}, shopifyConfig.storeUrl);
diff --git a/integrator-default-profile/samples/shopify-inventory-alert-via-twilio-sms/data_mappings.bal b/integrator-default-profile/samples/shopify-inventory-alert-via-twilio-sms/data_mappings.bal
new file mode 100644
index 00000000..e69de29b
diff --git a/integrator-default-profile/samples/shopify-inventory-alert-via-twilio-sms/functions.bal b/integrator-default-profile/samples/shopify-inventory-alert-via-twilio-sms/functions.bal
new file mode 100644
index 00000000..61857e6c
--- /dev/null
+++ b/integrator-default-profile/samples/shopify-inventory-alert-via-twilio-sms/functions.bal
@@ -0,0 +1,209 @@
+import ballerina/log;
+import ballerina/time;
+import ballerinax/trigger.shopify;
+import ballerinax/shopify.admin;
+import ballerinax/twilio;
+
+// Fetch current inventory for all variants of a product from Shopify Admin API.
+function getProductInventory(int productId) returns map|error {
+ admin:ProductList response = check adminClient->getProducts(ids = productId.toString());
+ anydata productsData = response["products"];
+ if productsData is () {
+ return {};
+ }
+ admin:Product[] adminProducts = check productsData.cloneWithType();
+ if adminProducts.length() == 0 {
+ return {};
+ }
+
+ map variantInventory = {};
+ admin:Product product = adminProducts[0];
+ string productTitle = product?.title ?: "";
+ admin:ProductVariant[]? variants = product?.variants;
+ if variants is admin:ProductVariant[] {
+ foreach admin:ProductVariant variant in variants {
+ int variantId = variant?.id ?: 0;
+ if variantId == 0 {
+ continue;
+ }
+ int? inventoryQty = variant?.inventory_quantity;
+ if inventoryQty is int {
+ variantInventory[variantId.toString()] = {
+ productId: productId,
+ variantId: variantId,
+ productName: productTitle,
+ variantTitle: variant?.title ?: "",
+ sku: variant?.sku ?: "",
+ inventory: inventoryQty
+ };
+ }
+ }
+ }
+ return variantInventory;
+}
+
+// Process line items from a new Shopify order: for each ordered product variant,
+// fetch current inventory and send an SMS alert if below the threshold.
+function processOrderedLineItems(shopify:LineItem[] lineItems) returns error? {
+ map