Create Triggers

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 the onUpdate 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 and onUpdate 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.