Join the #BuildWithBuildAgent Challenge! Get recognized, earn exclusive swag, and inspire the ServiceNow Community with what you can build using Build Agent.  Join the Challenge.

Ahmed Drar
Tera Guru
I've been dealing with a lot of third-party integrations lately that require ServiceNow to handle webhooks whenever certain events occur. So today, I want to highlight some key points about inbound webhooks and compare integration methods—using webhooks VS a polling-based approach.
 
Imagine This Scenario: 
 
A customer case is closed in ServiceNow. Shortly after, the customer receives an automated phone call from a third-party system asking them to rate the service from 1 to 5. Once they submit their rating, ServiceNow needs to update the original case with this feedback. But here's the catch: ServiceNow needs to stay informed about the status of the survey at all times.
 
In this scenario, I assume the system we integrate with supports webhook.
 
Let's explore two solutions to handle this but first, we need to know when to use webhook:
 
  • Messages triggered by specific events
  • Lightweight, minimal information payload
  • Push-based notifications to eliminate frequent polling
  • Only a simple acknowledgement is needed
 
Solution 1: Polling-Based Approach
 
1. POST REST message: ServiceNow tells the external system the case is closed and requests it to start the survey.
2. GET REST message: ServiceNow checks periodically to see the status and the rating from the survey.
3. Scheduled job or Flow: Runs regularly to check if the customer has completed the survey and updates the case accordingly.
 
This solution works, but it consumes unnecessary resources due to continuous polling and is a bit complex.
 
AhmedDrar_2-1741975319439.png
 
Let's have a look at Solution 2

 

Solution 2: Webhook-Based Approach

1. POST REST message: Same as before, letting the external system know the case is closed.
2. Scripted REST API: Receives webhook notifications directly from the third-party system once the survey is complete OR survey status changes
AhmedDrar_3-1741975347203.png
this solution looks appealing because we get real-time updates, we reduce resource usage and is much simpler and cleaner.

 

Example Webhook Payload

Here's what we would get from a third-party webhook:

{
  "web_hook_id": "0229302039",
  "survey": "complete",
  "case_number": "CS0123267",
  "customer_rating": 5
}

 

Here is how to create  Scripted REST API to receive webhook

  1. Navigate to: System Web Services > Scripted REST APIs.

  2. Click New, provide a name and a base API path, and click Submit.

  3. Under the created REST API, click Resources, then click New.

  4. Provide details:

    • Name: e.g., "Case Rating Update"

    • HTTP method: POST

AhmedDrar_0-1741557608775.png

AhmedDrar_1-1741558458113.png

 

 


When it comes to authentication, In this example, I generate a unique token using GlideSecureRandomUtil - getSecureRandomString, and store it as an encrypted system property. I pretend that the external system passes this as an API key in the request header.

 

Here's how to generate one securely:

 var secureRandom = GlideSecureRandomUtil;
 sToken = secureRandom.getSecureRandomString(25);

 

Here's a sample  of the script to handle webhook requests in scripted API:

(function process(request, response) {

    // Retrieve the request URL to validate incoming request origin
    var requestedURI = request.url;

    // Fetch the valid API key stored securely in system properties
    var validApiKey = gs.getProperty('sn_customerservice.webhook.api.key');

    // Get the API key provided in the request header for authentication
    var apiKeyFromHeader = request.getHeader('X-API-Key');

    // Validate the provided API key and ensure the request URL is from the trusted domain
    if (apiKeyFromHeader !== validApiKey || requestedURI.indexOf('trustedDomain') == -1) {
        // If validation fails, respond with a 403 Forbidden error
        response.setStatus(403);
        response.setBody({
            "error": "Forbidden",
            "message": "Unauthorized: invalid API key or domain"
        });
        return; // Terminate execution if validation fails
    }

    // Extract the JSON payload data from the webhook request body
    var body = request.body.data;

    // Obtain case number and customer rating from the payload
    var caseNumber = body.case_number;
    var customerRating = body.customer_rating;

    // Prepare to update the specific customer service case
    var grCS = new GlideRecord('sn_customerservice_case');
    grCS.addQuery('number', caseNumber); // Search case by its number
    grCS.query();

    // If the case record exists, update the customer rating
    if (grCS.next()) {
        grCS.u_customer_rating = customerRating; // Update rating field
        grCS.update(); // Commit changes

        // Send a successful response confirming the update
        response.setStatus(200);
        response.setBody({
            "message": "Case updated successfully."
        });
    } else {
        // If case not found, respond with a 404 Not Found error
        response.setStatus(404);
        response.setBody({
            "error": "Case not found."
        });
    }

})(request, response);

 

Comments
DJRIVAS
Tera Contributor

Thank you for sharing this, explanation is simple, yet the content is valuable for integration's design.

Version history
Last update:
‎03-15-2025 04:15 AM
Updated by:
Contributors