Creating BuildShip Triggers
Triggers are mechanisms to initiate a BuildShip Workflow. There are three main types of BuildShip Triggers:
1. API-based Triggers
These are triggered via any API Call. For example, the Rowy Trigger: The BuildShip workflow gets triggered when the BuildShip Workflow endpoint is called via the Rowy Extension.
2. Webhook-based Triggers
These are triggered by adding the BuildShip Workflow endpoint to a third-party service to receive a webhook call for specified events with the event payload. Examples include LemonSqueezy, GitHub Trigger, and RevenueCat Trigger.
Webhook-based triggers are preferably automatic. When a BuildShip Workflow is deployed or updated, the configuration is automatically updated on the linked service via their API Endpoints to set up webhooks.
If a service doesn't support automatic setup via API, manual instructions need to be provided for users to add the API endpoint. (See example: RevenueCat Trigger (opens in a new tab))
3. Schedule-based Triggers
These are cron-based triggers called by the GCP Cloud Scheduler at specified times/schedules. Examples include:
Trigger Authentication
Third-party triggers require authentication to connect with other services, either via API Keys or OAuth Apps.
API Key Authentication
The trigger and documentation should provide details on how users can easily find API Keys on the third-party service. For example:
OAuth App Authentication
If an OAuth App is required, contact the BuildShip Team to set up and verify the OAuth App. Here are some examples:
Trigger Logic
Trigger Logic is built on four lifecycle functions: onCreate
, onUpdate
, onExecution
, and onDelete
.
onCreate
- Runs only once when the workflow is shipped for the very first time
- This is the function which should include the API Call to set up the webhook endpoint.
💡
If the automated webhook set up fails on the first try, this needs to be handled in the
onUpdate
function. Check theonUpdate
example.
Sample onCreate
method for Lemon Squeezy Trigger:
const onCreate = async (
{ apiKey, events, secretKey, storeId }, // trigger inputs
{ workflow, runtimeUrl, env },
) => {
const webhookUrl = runtimeUrl + '/executeWorkflow/' + workflow.id;
const addWebhookResp = await fetch('https://api.lemonsqueezy.com/v1/webhooks', {
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/vnd.api+json',
},
method: 'POST',
// body according to the lemonsqueezy docs
body: JSON.stringify({
data: {
type: 'webhooks',
attributes: {
test_mode: true,
url: webhookUrl,
events: events,
secret: secretKey,
},
relationships: {
store: {
data: {
type: 'stores',
id: storeId,
},
},
},
},
}),
});
const resJson = await addWebhookResp.json();
const id = resJson.data?.id;
// setting the webhookId as workflow level env variable (required for deletion and updation)
await env.set([{ name: 'webhookId', value: id }]);
if (!addWebhookResp.ok) {
const error = await resJson;
return { success: false, message: 'Failed to create webhook' };
}
};
onUpdate
- Runs on every subsequent deployment of the workflow after the first time
- Add API Calls to update the Webhook endpoint here
Sample onUpdate
method for Lemon Squeezy Trigger:
const onUpdate = async ({ apiKey, secretKey, events, storeId }, { workflow, runtimeUrl, env }) => {
try {
const webhookId = env.get('webhookId');
if (!webhookId) {
throw new Error('webhookId is not set');
}
const res = await fetch(`https://api.lemonsqueezy.com/v1/webhooks/${webhookId}`, {
method: 'PATCH',
headers: {
Accept: 'application/vnd.api+json',
'Content-Type': 'application/vnd.api+json',
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify({
data: {
type: 'webhooks',
id: webhookId,
attributes: {
events: events,
},
},
}),
});
return res;
} catch (error) {
// if the webhook updation fails, create one instead
const webhookUrl = runtimeUrl + '/executeWorkflow/' + workflow.id;
const addWebhookResp = await fetch('https://api.lemonsqueezy.com/v1/webhooks', {
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/vnd.api+json',
},
method: 'POST',
body: JSON.stringify({
data: {
type: 'webhooks',
attributes: {
test_mode: true,
url: webhookUrl,
events: events,
secret: secretKey,
},
relationships: {
store: {
data: {
type: 'stores',
id: storeId,
},
},
},
},
}),
});
const resJson = await addWebhookResp.json();
const id = resJson.data.id;
await env.set([{ name: 'webhookId', value: id }]);
if (!addWebhookResp.ok) {
const error = await addWebhookResp.json();
return { success: false, message: 'Failed to create webhook' };
}
}
};
onExecution
- This is the method which runs on every execution.
- This is where we need to add payload verification (if applicable) and manually parse the incoming request body. (check the example below)
Sample onExecution
method for Lemon Squeezy Trigger:
const onExecution = async ({ secretKey }, { nodeReq, request, logging }) => {
const body = await parser['json'](nodeReq, { returnRawBody: true });
validate(secretKey, body.raw, request.headers);
const ret = {
query: request.query,
headers: request.headers,
body: body !== null && body !== void 0 ? body : {},
};
return {
request: ret,
output: ret.body,
};
};
// custom function to validate the raw request body (as specified by LemonSqueezy)
function validate(secretKey, body, headers) {
const hmac = crypto.createHmac('sha256', secretKey);
const hmacUpd = hmac.update(body).digest('hex');
const digest = Buffer.from(hmacUpd, 'utf8');
const signature = Buffer.from(headers['x-signature'] || '', 'utf8');
if (!crypto.timingSafeEqual(digest, signature)) {
throw new Error('Invalid signature.');
}
console.log('signature is valid');
}
onDelete
- The clean up function which runs every time the workflow or the trigger is deleted.
- This is where we need to add the API Call for removing the webhook set-up by the
onCreate
andonUpdate
functions.
Sample onDelete
method for Lemon Squeezy Trigger:
const onDelete = async ({ apiKey }, { env }) => {
const webhookId = env.get('webhookId');
console.log(webhookId);
if (webhookId) {
await fetch(`https://api.lemonsqueezy.com/v1/webhooks/${webhookId}`, {
method: 'DELETE',
headers: {
Accept: 'application/vnd.api+json',
'Content-Type': 'application/vnd.api+json',
Authorization: `Bearer ${apiKey}`,
},
});
await env.delete(['webhookId']);
console.log(`env deleted`);
return true;
} else {
return true;
}
};
NOTE: All of these functions would have to be exported, like:
export default {
onCreate,
onUpdate,
onExecution,
onDelete,
};
TIP: If we are planning to re-use the onCreate
functionality for every subsequent onUpdate
function as well, we
can do:
export default {
onCreate,
onUpdate: onCreate,
onExecution,
onDelete,
};
Need Help?
- 💬Join BuildShip Community
An active and large community of no-code / low-code builders. Ask questions, share feedback, showcase your project and connect with other BuildShip enthusiasts.
- 🙋Hire a BuildShip Expert
Need personalized help to build your product fast? Browse and hire from a range of independent freelancers, agencies and builders - all well versed with BuildShip.
- 🛟Send a Support Request
Got a specific question on your workflows / project or want to report a bug? Send a us a request using the "Support" button directly from your BuildShip Dashboard.
- ⭐️Feature Request
Something missing in BuildShip for you? Share on the #FeatureRequest channel on Discord. Also browse and cast your votes on other feature requests.