Webhooks
Setting up a webhook allows you to respond to actions generated by UserHub within your application. Once established, UserHub will deliver actions via an HTTP POST to your webhook endpoint:
POST /your-webhook-url?action=events.handle
This guide will walk you through setting up a handler for the Events action.
Setup project
First you'll need to create a project directory and install the required dependencies.
mkdir node-webhooks
cd node-webhooks
npm install express@4 tsx @types/express @userhub/sdk
mkdir go-webhooks
cd go-webhooks
go mod init example.com/webhooks
go get -u github.com/userhubdev/go-sdk
mkdir php-webhooks
cd php-webhooks
composer require userhub/sdk
mkdir python-webhooks
cd python-webhooks
python3 -m venv .venv
source .venv/bin/activate
pip install 'flask>=3,<4' userhub-sdk
You can find examples for other frameworks by clicking examples on the Client SDKs page.
Setup handler
Next you'll need to set up your app to listen for UserHub webhook requests.
import { Webhook, WebhookRequest } from "@userhub/sdk";
import express from "express";
const port = process.env.PORT || "8000";
const signingSecret = process.env.SIGNING_SECRET;
if (!signingSecret) {
console.error("SIGNING_SECRET required");
process.exit(1);
}
const wh = new Webhook(signingSecret);
wh.onEvent((event) => {
console.log("Event:", event.type);
if (event.type === "organizations.changed") {
const organization = event.organizationsChanged!.organization;
console.log(" - Organization:", organization.id, organization.displayName);
} else if (event.type === "users.changed") {
const user = event.usersChanged!.user;
console.log(" - User:", user.id, user.displayName);
}
});
const apiRouter = express.Router();
apiRouter.use(express.json());
// POST /api/ping
apiRouter.post("/ping", (req, res) => {
res.status(200).send(req.body);
});
const webhookRouter = express.Router();
webhookRouter.use(express.raw({ type: "*/*" }));
// POST /webhook
webhookRouter.post("", async (req, res) => {
const r = await wh.handle({
headers: req.headers,
body: req.body,
});
for (const [name, value] of r.headers.entries()) {
res.append(name, value);
}
res.status(r.statusCode).send(r.body);
});
const app = express();
app.use("/api", apiRouter);
app.use("/webhook", webhookRouter);
app.listen(port, () => {
console.log(`Listening on http://127.0.0.1:${port}`);
});
package main
import (
"context"
"fmt"
"net/http"
"os"
"github.com/userhubdev/go-sdk/eventsv1"
"github.com/userhubdev/go-sdk/webhook"
)
func run() error {
port := os.Getenv("PORT")
if port == "" {
port = "8000"
}
signingSecret := os.Getenv("SIGNING_SECRET")
if signingSecret == "" {
return fmt.Errorf("SIGNING_SECRET required")
}
wh := webhook.New(signingSecret)
wh.OnEvent(func(ctx context.Context, event *eventsv1.Event) error {
fmt.Println("Event:", event.Type)
if event.Type == "organizations.changed" {
organization := event.OrganizationsChanged.Organization
fmt.Println(" - Organization:", organization.Id, organization.DisplayName)
} else if event.Type == "users.changed" {
user := event.UsersChanged.User
fmt.Println(" - User:", user.Id, user.DisplayName)
}
return nil
})
fmt.Println("Listening on http://127.0.0.1:"+port)
return http.ListenAndServe(":"+port, wh)
}
func main() {
if err := run(); err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
}
<?php
declare(strict_types=1);
require __DIR__.'/vendor/autoload.php';
use UserHub\EventsV1\Event;
use UserHub\Webhook;
if (PHP_SAPI === 'cli') {
$port = getenv('PORT') ?: '8000';
echo "php -S localhost:{$port} ".__FILE__.PHP_EOL;
exit(1);
}
$signingSecret = getenv('SIGNING_SECRET');
if (empty($signingSecret)) {
echo 'SIGNING_SECRET required'.PHP_EOL;
exit(1);
}
$wh = new Webhook($signingSecret);
$wh->onEvent(static function (Event $event): void {
error_log('Event: '.$event->type);
if ($event->type === 'organizations.changed') {
$organization = $event->organizationsChanged->organization;
error_log(" - Organization: {$organization->id} {$organization->displayName}");
} elseif ($event->type === 'users.changed') {
$user = $event->usersChanged->user;
error_log(" - User: {$user->id} {$user->displayName}");
}
});
$wh->handleFromGlobals();
import os
from flask import Flask, Response, request
from userhub_sdk import eventsv1
from userhub_sdk.webhook import Webhook
signing_secret = os.environ.get("SIGNING_SECRET")
if not signing_secret:
raise RuntimeError("SIGNING_SECRET required")
def handle_event(event: eventsv1.Event):
print("Event:", event.type)
if event.type == "organizations.changed":
organization = event.organizations_changed.organization
print(" - Organization:", organization.id, organization.display_name)
elif event.type == "users.changed":
user = event.users_changed.user
print(" - User:", user.id, user.display_name)
wh = Webhook(signing_secret)
wh.on_event(handle_event)
app = Flask("flask-webhook")
@app.post("/webhook")
def handle_webhook():
res = wh(headers=dict(request.headers), body=request.get_data())
return Response(res.body, status=res.status_code, headers=res.headers)
Setup tunnel
Next you'll need to expose your app via a public URL.
If you already have a preferred method for doing this you can skip this step, otherwise follow the instructions below to sign up for a free ngrok account.
- Sign up for ngrok.
- Install the CLI client:
brew install ngrok
- Setup the ngrok authtoken:
ngrok config add-authtoken stp1KM0tsJgSZQ...
- Create a free static domain via Domains under Cloud Edge in the ngrok dashboard.
- Copy and paste the start command into your terminal:
ngrok http --domain=<your-name-here>.ngrok-free.app 8000
Create webhook
Next you'll need to create a webhook in UserHub and configure it to call your app.
- Go to the Admin console and click Webhooks via the Developers dropdown or Tenant settings
- Click New webhook
- Set the URL field to the public URL for your webhook:
https://<your-name-here>.ngrok-free.app/webhook
- Click Save
- Copy the signing secret to your clipboard
Tip: keep this page open, you'll be using it again shortly.
Start app
Set the signing secret environment variable to the value you copied in the previous step and start your app server.
First set the SIGNING_SECRET
environment variable:
export SIGNING_SECRET=6B5yry...
And then start your app:
npx tsx main.ts
go run .
php -S localhost:8000 main.php
flask --app main run --port 8000
Activate webhook
To activate the webhook you'll need to successfully test it, to do this:
- Navigate to the webhook we created above
- Click the Activate button
If activation succeeds, the status should change from pending setup to active.
Subscribe to event
To receive events you need to subscribe to them, to do this:
- Navigate to the webhook we created above
- Check the
organizations.changed
checkbox under Send events - Click Save
Send event
To send your first event:
- Go to Organizations in the Admin console
- Click the New organization button
- Enter a Display name
- Click Save
You should see an organizations.changed
event delivered to your webhook.