Integrate with any access control system using Webhooks

Integrations use webhooks to receive real-time events from Storeganise, such as move-ins, move-outs, transfers, and unit rental updates. The provided webhook handler, hosted on napkin.io, efficiently manages these events and communicates with Access Control systems and Storeganise APIs to perform access control actions.

Webhook Events Handled

  1. Move-In Completion
    • Description: Handles the completion of unit move-ins.
    • Actions: Creates group memberships, updates access codes, and sets up access PINs.
  2. Move-Out Completion
    • Description: Handles the completion of unit move-outs.
    • Actions: Revokes group memberships.
  3. Unit Transfer Completion
    • Description: Handles the completion of unit transfers.
    • Actions: Revoke group memberships for the old unit and set up access for the new unit.
  4. Unit Rental Update
    • Description: Handles updates to unit rentals, including PIN changes and overlocking status.
    • Actions: Updates access codes and overlocking status.
  5. Unit Overdue Marking/Unmarking
    • Description: Handles marking and unmarking units as overdue.
    • Actions: Implements access control measures based on overlocking status.

List of Security Systems We Currently Work With

Storeganise API key & webhook setup

Getting Started

  1. Webhook Configuration: Set up a webhook on napkin.io to receive Storeganise events.
    1. Create a webhook in https://{business}.storeganise.com/admin/settings/developer, with the url of your webhook handler (it can be AWS lambda, napkin.io, other services or a custom server) with events: job.unit_moveIn.completed, job.unit_moveOut.completed, job.unit_transfer.completed, unitRental.markOverdue, unitRental.unmarkOverdue, unitRental.updated
    2. Create customFields you’ll need in your integration in https://{business}.storeganise.com/admin/settings/custom-fields, for example unit.customFields.rubik_id, unitRental.customFields.rubik_pinCode, unitRental.customFields.rubik_overlocked (we use this convention to prefix all custom fields with the integration name, here “rubik”)
  2. Credentials: Create API keys in Storeganise and obtain credentials for your access control API
    1. Create an API key with admin role in https://{business}.storeganise.com/admin/settings/developer
    2. Contact the commercial support, for example: sales@sensorberg.com,
  3. Deploy the Handler: Host the provided webhook handler code and configure environment variables. (Storeganise can also host the code upon request)
  4. Test and Monitor: Thoroughly test the integration and monitor webhook events for real-time access control updates.

Code Snippet - Core template for an integration

The main request handler receives events from Storeganise (move-in, move-out, transfer, overlocking) and call the access control API to synchronize these changes

async function fetchSg(subpath, opts = {}) {
  return fetch(`https://{business}.storeganise.com/api/v1/admin/${subpath}${subpath.includes('?') ? '&' : '?'}include=customFields`, {
    method: opts.method,
    headers: {
      Authorization: `ApiKey ${process.env.API_KEY}`,
      ...opts.body && { 'Content-Type': 'application/json' },
    },
    body: opts.body && JSON.stringify(opts.body),
  })
    .then(async r => {
      const data = await r.json().catch(() => ({}));
      if (r.ok) return data;
      throw Object.assign(new Error('sg'), { status: r.status, ...data.error });
    });
}

export default async (req, res) => {
  if (!req.body?.data?.jobId) return res.json({ message: 'missing jobId, ignore' });

  try {
    const job = await fetchSg(`/v1/admin/jobs/${req.body.data?.jobId}`);

    switch (req.body.type) {
      case 'job.unit_moveIn.completed': {
        const [rental, unit, owner] = await Promise.all([
          fetchSg(`unit-rentals/${job.result.unitRentalId}`),
          fetchSg(`units/${job.result.unitId}`),
          fetchSg(`users/${job.result.ownerId}`),
        ]);

        // Your logic here for renting a unit using custom fields
        // you would use node-fetch for calling the access control API
        // and you can also call the Storeganise API for setting the unitRental custom fields (access codes for example)

        break;
      }
      
       case 'job.unit_moveOut.completed': {
        const [rental, unit] = await Promise.all([
          fetchSg(`unit-rentals/${job.result.unitRentalId}`),
          fetchSg(`units/${job.result.unitId}`),
        ]);

        // your logic for vacating unit
        break;
      }

      case 'job.unit_transfer.completed': {
        const [oldRental, newRental, oldUnit, newUnit] = await Promise.all([
          fetchSg(`unit-rentals/${job.result.oldRentalId}`),
          fetchSg(`unit-rentals/${job.result.newRentalId}`),
          fetchSg(`units/${job.result.oldUnitId}`),
          fetchSg(`units/${job.result.newUnitId}`),
        ]);
        const owner = await fetchSg(`users/${job.result.ownerId}`);
        
        // your logic, usually vacate old unit and rent new one, usually reusing code from previous events
        break;
      }
      
      case 'unitRental.updated': {
        const rental = await fetchSg(`unit-rentals/${job.result.unitRentalId}`);
        const [owner, unit] = await Promise.all([
          fetchSg(`users/${rental.ownerId}`),
          fetchSg(`units/${rental.unitId}`),
        ]);

        // your logic to update the rental access codes and/or overlocking status using custom fields
        break;
      }

      case 'unitRental.markOverdue':
      case 'unitRental.unmarkOverdue': {
        const rentals = await fetchSg(`unit-rentals?ids=${job.result.unitRentalIds || job.result.unitRentalId}`);
        
        // your logic for updating those rental using custom fields
        break;
      }
    }

    res.json({ message: 'ok ' + req.body.type });
  } catch(err) {
    console.error(err);
    res.status(err.status || 400).json({ message: err.message });
  }
}
Did this answer your question? Thanks for the feedback There was a problem submitting your feedback. Please try again later.

Still need help? Contact Us Contact Us