More and more Salesforce projects are being built into a large-scale ecosystem connecting CRMS, ERPs, OMS, payment platforms, and communication tools into a single integrated system. Most of these integrations follow well-known patterns, utilizing an out-of-the-box Salesforce connector or an OAuth integration pattern.
However, some of them require asynchronous integration when a certain event occurs: payment status changes, document signature confirmation, client verification process completed, etc.
These integrations rely on a webhook pattern, where an endpoint is configured and called with a fixed payload and needs to be handled by the server. Our only solution is to implement an anonymous API call to Salesforce. This approach falls short of meeting Salesforce’s recommendations for a secure and well-patterned integration.
So, how can this kind of integration be implemented?
Solution
The first idea that came to mind is to create a custom Apex class that follows a webhook pattern. But exposing a web service using a site.com isn’t what I had in mind – I prefer to do it the right way and solve our problem of anonymous API calls.
A middleware with an integrated Salesforce connector can also be used in some cases, but some middlewares are pricey and combine a lot of features that are not needed for a simple integration.
As a technical architect, I always have the curiosity to learn new ways of implementation and to challenge myself to find better solutions. Rather than relying on the middleware, the goal here is to build and host our own web service and customize it to fit your needs.
For this, I chose to code with NodeJS and host my code on Render.
Comparing Middleware with Custom Solutions
When comparing these two solutions, it is important to avoid categorizing one as easy and the other one as difficult. In practice, both approaches are of medium complexity, with the level of effort depending on the team’s experience and resources.
Middlewares may seem to be challenging at first, with teams that are not familiar with the tool, but it is relatively easy to learn and adopt. Similarly, a custom solution can be easy to implement when the technical expertise and resources are in place. Therefore, any assessment of complexity should be qualified by considering the existing knowledge, skills, and resources.
| Middleware | Custom solution | |
|---|---|---|
| Cost | High: Enterprise Licensing. | Low: Pay for hosting services. |
| Setup | Medium: Middleware expertise required. | Medium: Code with any language that supports web service hosting. |
| Integration | Built-in: Pre-configured / connectors available. | Custom: Connect to Salesforce using APIs. |
| Webhook handling | Medium: Configuration needed. | Custom: Built by code the easy way. |
| Maintenance | Vendor managed. | Self managed. |
| Monitoring and logging | Built-in / Custom: Some middleware requires a customized solution. | Custom: Easier to integrate your own logging with other services on the same or other hosted services with 100% more flexibility. |
Architecture Design

The cloud server hosting our web service has two exposed endpoints (as the schema shows, those endpoints can filter the incoming IPs and allow only the third party – we will not go into details as there are many methods to implement this).
- GET: This call is made only when configuring the callback URL in the third party for the first time. A secret token is already configured in the Cloud Server as an environment variable. The third party calls the endpoint, and if both secret tokens match, then the endpoint is validated (some third parties don’t follow the webhook pattern 100% – the GET request is omitted and the callback is made without the first validation).
- POST: This is the call that is made to send the payload with the real data when calling the callback URL.
- The cloud server has the Salesforce variables configured (will go into details in the configuration section) – an external client application is also configured on Salesforce’s side.
- Access token request (POST): JWT is created and signed with the private key. An API call is made (JWT Bearer Flow). The authorization server checks the signature against the public key of the certificate to verify the authenticity of the request.
- Once the server verifies the transaction, a token is sent to the Cloud server.
- The Cloud server securely makes an API call to Salesforce with the token.
Code Explanation
The code sample can be downloaded from GitHub. Note that the provided project is coded with minimal security implementations for the purpose of showing a basic hosted web service. Make sure all the security implementations are in place and validated by your security team.
For the project, we have the app.js file where all the main code will run and package.json where we can find the project metadata, dependencies, and other elements in a NodeJS project.
The code is commented in details. For the big parts, we have the following:
- A section where the environment variables are fetched:
const sfClientId = process.env.SF_CLIENT_ID;
- Two main functions. These are the methods that represent the methods for the GET and POST calls:
app.get()app.post()
- Utility methods:
getAssertionToken(): used to build the JWT payload and sign it with the RSA key.getSalesforceToken(): used to call the authorization server to get the token.
Additionally, we can improve the existing code by :
- A function where your custom/standard API call to Salesforce’s logic is written.
- An optional section checking IP ranges and signatures in order to block unauthorized access.
Cloud Hosting Configuration: Step-By-Step
- Firstly, we will need to create the RSA public and private keys. To do that, you will need to use your PC’s terminal and run the following commands:
- Generate RSA private key (This key is the private key that will be used in your Render app in the JWT_PRIVATE variable.): openssl genrsa -out private.key 2048
- Generate a self-signed public certificate (This key is the public key that will be used in your Salesforce’s external client app.):
openssl req -new -x509 -key private.key -out public.crt -days 3650 \
-subj "/C=FR/ST=Ile-de-France/L=Paris/O=MyCompany/CN=my-integration"
- Create an integration user and give it the necessary access (Permission Sets, Profiles, etc.)
- Create the external client application in Salesforce:
- Configure the App Policies section to fit your needs (Permission Sets, Profiles, etc.).
- Navigate to the Settings tab.
- Configure your OAuth Scopes.
- In the Flow Enablement section, check “Enable JWT Bearer Flow” and upload your RSA public key.

- Take the hosted code and open it with VS Code. Make your desired changes to handle the incoming payload (depending on your third party), transformations and other code logic, and create a new GIT repository (Render will use this repository and the branch that you specify).
- Go to Render and create your account. Create a workspace and a project. You can also follow Render’s instructions.
- Now, click on “New Service,” then “Web Service,” and connect your repository.
- Fill in the required information. In the Instance Type, you can choose to upgrade to a more powerful hosting version (paid version).
- In Build Command, write:
npm install express, and in Start Command writenode app.js - Then you will need to assign the environment variables:

| JWT_PRIVATE | RSA private key generated in your terminal |
| SF_CLIENT_ID | Key from the external client application in Salesforce |
| SF_TOKEN_AUD | The Salesforce org URL |
| SF_TOKEN_URL | The Salesforce authorization server URL (https://login.salesforce.com/services/oauth2/token for Production) |
| SF_USERNAME | Your integration user’s username |
| VERIFY_TOKEN | The token used by the third party to verify your webhook |
- Render will deploy your web service and notify you by email when it is done or if there are errors to fix.
- Once validated and deployed, you can navigate to the Events tab on the left side and get your endpoint URL (the purple URL that ends with onrender.com).

- Congratulations, your web service is hosted and can be used by third parties. Testing can be tricky – standard tests can be made using Postman.
Final Thoughts
Modern ecosystems around Salesforce rarely are in isolation. While OAuth authentications and connectors handle the integrations, webhook-driven integrations expose security and aren’t flexible when it comes to integrating with Salesforce.
Replying on an anonymous API in Salesforce works, but it is not aligned with best practices and introduces avoidable risks.
Building a custom application with a programming language and hosting it online offers an alternative by providing full control without the overhead or cost of a full-scale middleware solution.
The next time a third-party application doesn’t have the ability to customize its callback URL, think of your options. Is it best to use a middleware, or dedicate a team to implementing a fully customized web app?







