Last updated

Create a plan event-handling endpoint

Smartsheet plan-level webhooks send callbacks carrying user-plan-related event payloads, including user seat type update events. You receive the callbacks at an HTTPS POST endpoint that you designate for the webhook (webhook setup covers it).

Your endpoint must respond to Smartsheet verification challenges (Smartsheet pings the target endpoint to ensure it's listening) and should parse the payload's user seat type update events.

After extracting the event data you want, you can pass it along to your approval workflows.

Note: You can write your endpoint application in whatever language you like and deploy it on any web application server that satisfies the prerequisites mentioned here.

This article provides an example of an endpoint application, describes its key components, and calls out where you can add your own logic for handling user type updates.

Prerequisites

  • Smartsheet API access token. You can generate an access token (key) in the Smartsheet UI.

  • A web framework that supports hosting REST endpoints.

  • Endpoint host server with a valid SSL certificate.

    Important: Smartsheet webhooks don't support self-signed certificates.

Example endpoint

Here's an example endpoint application for receiving and parsing user seat type update events. The application is written in NodeJS and is built to deploy on an Express.js web application server.

// This ExpressApp demonstrates how to handle Smartsheet webhook verification and plan-level webhook callbacks.
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const port = process.env.PORT || 3000; // Set the server port however you like.
const YOUR_HTTP_ENDPOINT = '/smartsheet-webhook'; // Replace with your endpoint path.

app.use(bodyParser.json());
app.post(YOUR_HTTP_ENDPOINT, (req, res) => {
    console.log(`Received POST request to ${YOUR_HTTP_ENDPOINT}`); // Optionally, log a message.

    // Check if the request body contains a 'challenge' property.
    if (req.body && req.body.challenge) {
        const challengeValue = req.body.challenge;

        // Respond with a Smartsheet-Hook-Response header containing the challenge value
        res.set('Smartsheet-Hook-Response', challengeValue);
        res.status(200).send('Webhook verification successful.');
    } else if (req.body && req.body.events) {
        // This is a Smartsheet webhook event notification
        const events = req.body.events;
        events.forEach((event) => {
            if event && event.seatType { // Is this event a seatType event?
                if (event.seatType == "PROVISIONAL_MEMBER") {
                    // TODO: Add your logic here to process the Smartsheet event payload
                    // For example,
                    //   triggerApprovalProcess(payload.emailAddress, payload.seatType)
                }
            }
        });
        // For now, send a 200 OK to acknowledge receipt
        res.status(200).send('Webhook event received.');
    }
});
app.listen(port);

The following sections describe the code.

Port number

The application listens on a port set in the local environment (its value is process.env.PORT). You can set a valid port however you like.

Valid ports: Here are the valid ports for hosting Smartsheet webhook event-handling endpoints.

  • 443 (default for HTTPS)
  • 8000
  • 8008
  • 8080
  • 8443

Endpoint path

This endpoint's path is /smartsheet-webhook. Replace this path with a path you prefer.

Request types

When receiving HTTPS POST requests, the app prints to the log and then checks the request body to determine if it's a verification request or a plan callback

  • Smartsheet verification challenge: Smartsheet sends these ongoing requests to ensure you're listening. For more details, see Webhook verification.

  • Plan callback: These requests have an events payload that can include user seat type update events.

If the request is a verification challenge, the endpoint satisfies the verification by returning a 200 status code and the request challenge value in a 'Smartsheet-Hook-Response` response header.

If the request has an events field, the request is a plan-scoped callback. The endpoint iterates over the events in the events array field.

Process the payload

The // TODO ... comment marks where you can insert code to handle user events where the user updated to PROVISIONAL_MEMBER.

Here's an example plan-scoped callback, like the ones you can process.

{
  "scopeObjectId": 3285357287499652,
  "webhookId": 8444254503626628,
  "newWebhookStatus": "ENABLED",
  "nonce": "string",
  "timestamp": "2019-08-24T14:15:22Z",
  "events": [
    {
      "planId": 3285357287499652,
      "userId": 8846083322341252,
      "emailAddress": "test.user@example.com",
      "seatType": "PROVISIONAL_MEMBER",
      "isInternal": true,
      "lastBillableActivity": "2025-08-13T12:00:00Z",
      "timestamp": "2025-08-28T15:00:00Z",
      "updatedByUserId": 48569348493401210,
      "objectType": "user.seatType",
      "eventType": "updated"
    }
  ],
  "scope": "plan"
}

The following fields from the example event confirm this is a user seat type update.

"objectType": "user.seatType",
"eventType": "updated"

The user's seat type was updated to PROVISIONAL_MEMBER.

"seatType": "PROVISIONAL_MEMBER"

The event includes userId, emailAddress, and isInternal fields, which can be helpful in your approval/denial decision criteria.

With an endpoint such as the one above, that receives plan-scoped callbacks, you can extract user seat type update events and use the event data to populate internal approval systems or trigger internal approval workflows.

See also: The user seat type update event schema here -- the schema page includes an example object.

What's next?

Launch a plan-level webhook to test receiving notifications at this endpoint.