Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/projects.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
]
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
A(["Begin"]):::startNode
B["Receive Shopify Order Webhook"]:::processNode
C{"Inventory Below <br/> Threshold?"}:::decisionNode
D{"Cooldown <br/> 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
Original file line number Diff line number Diff line change
@@ -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

<details>

<summary>Shopify Setup Guide</summary>

1. Log in to your Shopify Admin. Your store URL follows the pattern `https://<your-store-name>.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**.

</details>

<details>

<summary>Twilio Setup Guide</summary>

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.

</details>

<details>

<summary>Additional Configurations</summary>

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}}`

</details>
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Loading