├── project
├── build.properties
└── plugins.sbt
├── sbt-launch.jar
├── public
└── images
│ └── favicon.png
├── app
├── assets
│ ├── stylesheets
│ │ └── main.css
│ └── javascripts
│ │ └── app.js
├── apextemplates
│ ├── triggers
│ │ └── WebhookTrigger.scala.txt
│ └── classes
│ │ ├── Webhook.scala.txt
│ │ └── TriggerTest.scala.txt
├── core
│ ├── TriggerMetadata.scala
│ └── TriggerEvent.scala
├── views
│ ├── index.scala.html
│ └── app.scala.html
├── controllers
│ └── Application.scala
└── utils
│ └── ForceUtil.scala
├── .gitignore
├── conf
├── application.conf
├── logback.xml
└── routes
├── sbt
├── app.json
├── LICENSE
├── README.md
├── test
├── ForceUtilSpec.scala
└── ApplicationSpec.scala
└── sbt.cmd
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.6.1
2 |
--------------------------------------------------------------------------------
/sbt-launch.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesward/salesforce-webhook-creator/HEAD/sbt-launch.jar
--------------------------------------------------------------------------------
/public/images/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesward/salesforce-webhook-creator/HEAD/public/images/favicon.png
--------------------------------------------------------------------------------
/app/assets/stylesheets/main.css:
--------------------------------------------------------------------------------
1 | body {
2 | padding-top: 60px;
3 | }
4 |
5 | [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
6 | display: none !important;
7 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | logs
2 | project/project
3 | project/target
4 | target
5 | tmp
6 | .history
7 | dist
8 | /.idea
9 | /*.iml
10 | /out
11 | /.idea_modules
12 | /.classpath
13 | /.project
14 | /RUNNING_PID
15 | /.settings
16 | .DS_Store
17 | /dev.env
18 | /.bsp/
19 |
--------------------------------------------------------------------------------
/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.8.13")
2 |
3 | addSbtPlugin("com.typesafe.sbt" % "sbt-jshint" % "1.0.6")
4 |
5 | addSbtPlugin("com.typesafe.sbt" % "sbt-digest" % "1.1.4")
6 |
7 | addSbtPlugin("com.typesafe.sbt" % "sbt-gzip" % "1.0.2")
8 |
--------------------------------------------------------------------------------
/app/apextemplates/triggers/WebhookTrigger.scala.txt:
--------------------------------------------------------------------------------
1 | @(name: String, sobject: String, events: List[String], url: String)trigger @{name}WebhookTrigger on @{sobject} (@{events.mkString(",")}) {
2 |
3 | String url = '@url';
4 |
5 | String content = Webhook.jsonContent(Trigger.new, Trigger.old);
6 |
7 | Webhook.callout(url, content);
8 |
9 | }
--------------------------------------------------------------------------------
/app/core/TriggerMetadata.scala:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import core.TriggerEvent.TriggerEvent
4 | import play.api.libs.json.Json
5 |
6 | case class TriggerMetadata(name: String, sobject: String, events: List[TriggerEvent], url: String)
7 |
8 | object TriggerMetadata {
9 | implicit val reads = Json.reads[TriggerMetadata].map { triggerMetadata =>
10 | triggerMetadata.copy(name = triggerMetadata.name.replace(" ", ""))
11 | }
12 | implicit val writes = Json.writes[TriggerMetadata]
13 | }
14 |
--------------------------------------------------------------------------------
/conf/application.conf:
--------------------------------------------------------------------------------
1 | play.filters.enabled += play.filters.https.RedirectHttpsFilter
2 | play.filters.enabled += play.filters.gzip.GzipFilter
3 | play.filters.disabled += play.filters.hosts.AllowedHostsFilter
4 |
5 | play.http.forwarded.trustedProxies=["0.0.0.0/0", "::/0"]
6 |
7 | play.http.secret.key=${?APPLICATION_SECRET}
8 |
9 | play.application.langs=["en"]
10 |
11 | force.oauth.consumer-key=${FORCE_CONSUMER_KEY}
12 | force.oauth.consumer-secret=${FORCE_CONSUMER_SECRET}
13 |
14 | webjars.use-cdn=true
15 | play.filters.csp.CSPFilter="default-src 'self' 'unsafe-inline' cdn.jsdelivr.net"
16 |
--------------------------------------------------------------------------------
/app/core/TriggerEvent.scala:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import play.api.libs.json._
4 |
5 | object TriggerEvent extends Enumeration {
6 | type TriggerEvent = Value
7 | val BeforeInsert = Value("before insert")
8 | val BeforeUpdate = Value("before update")
9 | val BeforeDelete = Value("before delete")
10 | val AfterInsert = Value("after insert")
11 | val AfterUpdate = Value("after update")
12 | val AfterDelete = Value("after delete")
13 | val AfterUndelete = Value("after undelete")
14 |
15 | implicit val jsonFormat = new Format[TriggerEvent] {
16 | def reads(json: JsValue) = JsSuccess(TriggerEvent.withName(json.as[String]))
17 | def writes(triggerEvent: TriggerEvent) = JsString(triggerEvent.toString)
18 | }
19 | }
--------------------------------------------------------------------------------
/sbt:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | get_java_cmd() {
4 | if [[ -n "$JAVA_HOME" ]] && [[ -x "$JAVA_HOME/bin/java" ]]; then
5 | echo "$JAVA_HOME/bin/java"
6 | else
7 | echo "java"
8 | fi
9 | }
10 |
11 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
12 | move_to_project_dir() {
13 | if [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]]; then
14 | cd $(dirname $0)
15 | fi
16 | }
17 |
18 | move_to_project_dir
19 |
20 | SBT_LAUNCHER="$(dirname $0)/sbt-launch.jar"
21 |
22 | SBT_OPTS="-Xms512M -Xmx4048M -Xss4M -XX:+CMSClassUnloadingEnabled"
23 |
24 | # todo: check java cmd
25 |
26 | # todo: help text
27 |
28 | $(get_java_cmd) ${SBT_OPTS} -jar ${SBT_LAUNCHER} "$@"
29 |
--------------------------------------------------------------------------------
/conf/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | %coloredLevel %logger{15} - %message%n%xException{10}
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Salesforce Webhook Creator",
3 | "description": "Create Webhooks on Salesforce",
4 | "repository": "https://github.com/jamesward/salesforce-webhook-creator",
5 | "website": "http://www.jamesward.com/",
6 | "keywords": ["salesforce"],
7 | "env": {
8 | "APPLICATION_SECRET": {
9 | "description": "A secret key for crypto and signed cookies.",
10 | "generator": "secret"
11 | },
12 | "ASSETS_URL": {
13 | "description": "The WebJar CDN URL",
14 | "value": "//cdn.jsdelivr.net"
15 | },
16 | "FORCE_CONSUMER_KEY": {
17 | "description": "The Salesforce OAuth Consumer Key",
18 | "required": true
19 | },
20 | "FORCE_CONSUMER_SECRET": {
21 | "description": "The Salesforce OAuth Consumer Secret",
22 | "required": true
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/conf/routes:
--------------------------------------------------------------------------------
1 | # Routes
2 | # This file defines all application routes (Higher priority routes first)
3 | # ~~~~
4 |
5 | # Home page
6 | GET / controllers.Application.index
7 |
8 | GET /logout controllers.Application.logout
9 |
10 | GET /app controllers.Application.app(debug: Boolean ?= false)
11 |
12 | GET /sobjects controllers.Application.getSobjects
13 |
14 | GET /webhooks controllers.Application.getWebhooks
15 | POST /webhooks controllers.Application.createWebhook
16 |
17 | GET /_oauth_callback controllers.Application.oauthCallback(code, state)
18 |
19 | # Map static resources from the /public folder to the /assets URL path
20 | GET /vassets/*file controllers.Assets.versioned(path="/public", file: Asset)
21 | GET /assets/*file controllers.Assets.at(path="/public", file)
22 |
23 | -> /webjars webjars.Routes
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 James Ward
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/app/views/index.scala.html:
--------------------------------------------------------------------------------
1 | @this(forceUtil: utils.ForceUtil, webJarsUtil: org.webjars.play.WebJarsUtil)
2 | @(implicit request: RequestHeader)
3 |
4 |
5 |
6 | Salesforce Webhook Creator
7 | @webJarsUtil.locate("bootstrap.min.css").css()
8 |
9 |
10 |
11 |
12 |
19 |
20 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Salesforce Webhook Creator
2 |
3 | This is a simple web app that makes it easy to create Webhooks on Salesforce. For usage info see: http://www.jamesward.com/2014/06/30/create-webhooks-on-salesforce-com
4 |
5 | Either use a shared instance of this app: https://salesforce-webhook-creator.herokuapp.com/
6 |
7 | Or deploy your own instance on Heroku:
8 |
9 | 1. Create a new Connected App in Salesforce:
10 |
11 | 1. [Create a Connected App](https://login.salesforce.com/app/mgmt/forceconnectedapps/forceAppEdit.apexp)
12 | 1. Check `Enable OAuth Settings`
13 | 1. Set the `Callback URL` to `http://localhost:9000/_oauth_callback`
14 | 1. In `Available OAuth Scopes` select `Full access (full)` and click `Add`
15 | 1. Save the new Connected App and keep track of the Consumer Key & Consumer Secret for later use
16 |
17 | 1. Deploy this app on Heroku: [](https://heroku.com/deploy)
18 | 1. Edit the Connected App on Salesforce and update the `Callback URL` to be `https://YOUR_APP_NAME.herokuapp.com/_oauth_callback`
19 |
20 |
21 | ## Local Dev
22 |
23 | Run Locally:
24 | ```
25 | export FORCE_CONSUMER_KEY=YOUR_CONSUMER_KEY
26 | export FORCE_CONSUMER_SECRET=YOUR_CONSUMER_SECRET
27 |
28 | ./sbt run
29 | ```
30 |
31 | Test:
32 | ```
33 | export FORCE_CONSUMER_KEY=YOUR_CONSUMER_KEY
34 | export FORCE_CONSUMER_SECRET=YOUR_CONSUMER_SECRET
35 | export FORCE_USERNAME=YOUR_SALESFORCE_USERNAME
36 | export FORCE_PASSWORD=YOUR_SALESFORCE_PASSWORD
37 |
38 | ./sbt test
39 | ```
--------------------------------------------------------------------------------
/app/apextemplates/classes/Webhook.scala.txt:
--------------------------------------------------------------------------------
1 | public class Webhook implements HttpCalloutMock {
2 |
3 | public static HttpRequest request;
4 | public static HttpResponse response;
5 |
6 | public HTTPResponse respond(HTTPRequest req) {
7 | request = req;
8 | response = new HttpResponse();
9 | response.setStatusCode(200);
10 | return response;
11 | }
12 |
13 | public static String jsonContent(List