Build
Webhooks
Call an HTTP endpoint or send an email when a request to your service matches a condition, with the payload shaped by a template: Slack, Mailjet, your backend.
A webhook calls an HTTP endpoint or sends an email when a request to your service matches a condition you write. You set it up in the console, with no code to deploy.
How it works
For every request your service handles, each webhook evaluates its condition against the request and its response. When the condition matches, the webhook sends an HTTP POST or an email.
Webhooks run after the response has gone back to the client, so they never slow down or break a request. The webhooks of one request run one after another, and a failure in one does not stop the others.
Turn it on
-
Open Service → Webhooks.
-
Click Enable webhooks.
The badge beside the title says whether the feature is ENABLED or DISABLED. Disable stops every webhook and keeps their configuration.
Two kinds of webhook
HTTP webhooks
Send an HTTP POST to a public URL when the condition matches.
| Field | Required | Description |
|---|---|---|
|
Yes |
A name you recognise, such as "New User Slack Alert". |
|
Yes |
Predicate expression. See Conditions below. |
|
Yes |
Destination URL. Must be reachable from the internet. Use |
|
No |
Request timeout in milliseconds, from 1000 to 30000. Default: |
|
No |
JSON object of headers sent with the request, for example for authentication. |
|
No |
Boolean. When |
|
No |
Mustache template for the request body. See Shape the payload. |
Email webhooks
Send an email when the condition matches, through the service’s mail server.
|
Note
|
These count against your service’s daily email allowance. A webhook with several recipients takes them all or none: five addresses with three left sends nothing. With your own mail server no limit applies. |
| Field | Required | Description |
|---|---|---|
|
Yes |
A name you recognise. |
|
Yes |
Predicate expression. |
|
Yes |
Array of recipient email addresses. |
|
No |
Array of CC email addresses. |
|
Yes |
Subject line. Supports Mustache variables. |
|
Yes |
|
|
With |
JSON template with Mustache variables. The result is sent as an HTML table. |
|
With |
HTML template with Mustache variables. |
Manage webhooks
The list
The page lists your webhooks in a table you can search by name, URL or condition. Click a row to see its details and its buttons.
Create one
-
Click Add webhook.
-
Under Webhook type, choose HTTP Request or Email.
-
Fill in the fields.
-
Click Add webhook.
Edit one
Open the row and click Edit. The form opens with the current values. Click Save when done.
Delete one
Open the row and click Delete. The webhook is removed after you confirm.
Conditions
The condition field uses the Undertow predicate language, the same as permission rules. It is evaluated against the incoming request and its response.
Paths
path('/users') # exact path match
path-prefix('/orders') # any path starting with /orders
path-template('/users/{id}') # parameterised path
path-suffix('.csv') # path ending with .csv
Methods
method('POST')
method('PUT') or method('PATCH')
Combining them
path('/users') and method('POST')
path-prefix('/orders') and (method('POST') or method('PUT'))
path-template('/users/{id}') and method('DELETE')
not method('GET')
Common conditions
| Goal | Condition |
|---|---|
Fire when a new user is created |
|
Fire on any write to |
|
Fire when a specific resource is deleted |
|
|
Tip
|
Use Recording to see the real request paths before you write a condition, so it matches your actual traffic. |
Shape the payload
By default, the HTTP request body is a standard JSON payload that describes the event. Turn on transformation when the target expects its own format, such as Slack blocks or Mailjet’s API schema.
Transformations use Mustache templates with {{variable}} syntax.
Variables
| Variable | Description |
|---|---|
|
Identifier of the Ulabase service. |
|
ISO 8601 timestamp of the event. |
|
HTTP method of the triggering request (e.g. |
|
Path of the triggering request (e.g. |
|
Query string of the triggering request. |
|
Any field from the request body. Use dot notation for nested fields. |
|
Any header from the incoming request. |
|
HTTP status code of the response. |
|
Any field from the response body. |
|
Any header from the response. |
Example: Slack Block Kit
{
"text": "New event on {{service.id}}",
"blocks": [{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*{{request.method}}* on `{{request.path}}`\nStatus: {{response.statusCode}}"
}
}, {
"type": "context",
"elements": [{
"type": "mrkdwn",
"text": "{{timestamp}}"
}]
}]
}
Example: Mailjet Send API
{
"Messages": [{
"From": { "Email": "noreply@yourdomain.com", "Name": "Your App" },
"To": [{ "Email": "{{request.body.email}}", "Name": "{{request.body.name}}" }],
"Subject": "Welcome!",
"HTMLPart": "<h1>Hi {{request.body.name}}!</h1><p>Welcome to our service.</p>"
}]
}
Under the template, Preview transformation renders it on sample JSON as you type. Paste the sample yourself, or click Load from logs to use a request the webhook recorded. Email webhooks have the same preview for the subject and the body.
Recording
A webhook keeps logs only while it is recording. Open its row and click Record (60s): for the next 60 seconds, every request the service receives is logged with what this webhook did, whether the condition matched or not. Stop recording ends it early.
Use it to:
-
see the exact field names in real request bodies before you write a template;
-
check that your condition matches the paths and methods your clients actually use.
Execution logs
Open the row and click View logs to see what was recorded. Each entry shows:
-
Timestamp
-
Status:
success,failedorskipped -
Duration in milliseconds
-
A summary of the outgoing request and the response
Click an entry to see the details:
| Section | What you see |
|---|---|
Summary |
Status and duration. |
Incoming request |
Method and path of the request that triggered the webhook. |
Condition |
The expression, and whether it matched. |
Transformation |
The input data and the rendered output, when transformation is on. |
Outgoing request |
The HTTP request sent to the target URL. |
Response |
Status code, headers and body received from the target. |
The buttons All, Success, Failed and Skipped filter the list. The refresh icon reloads it, and Load more shows older entries. Logs are deleted after 30 days.
Testing
To test a webhook end to end, click Record (60s), send a request that matches the condition, and open View logs. During development, use https://httpbin.org/post as the target URL: it echoes back the payload it received, so you see what your service sends.
Tutorials
Slack notifications
Send a Slack message whenever a new user registers.
Step 1: Create a Slack incoming webhook
-
Click Create New App → From scratch, name the app, and select your workspace.
-
Open Incoming Webhooks, switch it ON, and click Add New Webhook to Workspace.
-
Select the target channel and copy the webhook URL.
Step 2: Configure it in Ulabase
Open Service → Webhooks and click Add webhook (type HTTP Request):
| Field | Value |
|---|---|
Name |
|
URL |
(your Slack webhook URL) |
Condition |
|
Timeout |
|
Transform |
Enabled |
Transformation template:
{
"text": "New User Registered",
"blocks": [{
"type": "header",
"text": { "type": "plain_text", "text": ":tada: New User Registered" }
}, {
"type": "section",
"fields": [
{ "type": "mrkdwn", "text": "*Name:*\n{{request.body.name}}" },
{ "type": "mrkdwn", "text": "*Email:*\n{{request.body.email}}" }
]
}, {
"type": "context",
"elements": [{ "type": "mrkdwn", "text": "Service: {{service.id}} | {{timestamp}}" }]
}]
}
Step 3: Test with a real request
Click Record (60s) on the webhook, then send:
curl -X POST "https://c0ffee.ulabase.app/users" \
-H "Authorization: Basic $(echo -n alice:secret | base64)" \
-H "Content-Type: application/json" \
-d '{"_id": "jane", "password": "s3cr3t", "roles": ["user"], "name": "Jane Smith", "email": "jane@example.com"}'
The message appears in Slack, and the entry under View logs.
More Slack templates
{
"text": "New Order",
"blocks": [{
"type": "section",
"text": {
"type": "mrkdwn",
"text": ":shopping_cart: *Order #{{response.body._id}}*\nCustomer: {{request.body.customerName}}\nTotal: ${{request.body.total}}"
}
}]
}
{
"text": "API Error",
"blocks": [{
"type": "section",
"text": {
"type": "mrkdwn",
"text": ":warning: *{{response.statusCode}}* on `{{request.method}} {{request.path}}`"
}
}]
}
Condition: method('POST') or method('PUT') or method('PATCH')
Email via Mailjet
Send a welcome email when a user registers.
Step 1: Set up Mailjet
-
Sign up at https://www.mailjet.com.
-
Go to Account Settings → API Key Management and copy your API Key and Secret Key.
-
Go to Sender Addresses and verify your sending email address.
-
Base64-encode your credentials:
echo -n "YOUR_API_KEY:YOUR_SECRET_KEY" | base64
Step 2: Configure it in Ulabase
| Field | Value |
|---|---|
Name |
|
URL |
|
Condition |
|
Timeout |
|
Headers |
|
Transform |
Enabled |
Transformation template:
{
"Messages": [{
"From": { "Email": "noreply@yourdomain.com", "Name": "Your App" },
"To": [{ "Email": "{{request.body.email}}", "Name": "{{request.body.name}}" }],
"Subject": "Welcome to Our Service!",
"TextPart": "Hi {{request.body.name}},\n\nWelcome aboard!\n\nBest regards,\nThe Team",
"HTMLPart": "<h1>Welcome {{request.body.name}}!</h1><p>We're glad to have you.</p>"
}]
}
More Mailjet templates
{
"Messages": [{
"From": { "Email": "orders@yourdomain.com", "Name": "Your Store" },
"To": [{ "Email": "{{request.body.customerEmail}}", "Name": "{{request.body.customerName}}" }],
"Subject": "Order Confirmation #{{response.body._id}}",
"HTMLPart": "<h1>Thank you!</h1><p>Order #{{response.body._id}} — Total: ${{request.body.total}}</p>"
}]
}
Email via SendGrid
| Field | Value |
|---|---|
URL |
|
Headers |
|
Transformation template:
{
"personalizations": [{ "to": [{ "email": "{{request.body.email}}", "name": "{{request.body.name}}" }] }],
"from": { "email": "noreply@yourdomain.com", "name": "Your App" },
"subject": "Welcome!",
"content": [{ "type": "text/html", "value": "<h1>Welcome {{request.body.name}}!</h1>" }]
}
Other targets
-
Discord: create an incoming webhook in the channel settings, and send a Slack-like payload.
-
Microsoft Teams: create an incoming webhook connector, and use the MessageCard JSON format.
-
Your own API: read the target’s API documentation, write a matching template, and test it with
https://httpbin.org/postfirst.
Good practice
-
Write specific conditions, so a webhook does not fire on every request.
-
Record and check the logs before you rely on a webhook.
-
Set realistic timeouts: 5 to 10 seconds covers most external APIs.
-
Use HTTPS target URLs.
-
Send only the fields the target needs.
-
Several webhooks can share a condition. They all fire, one after another, and a failure in one does not affect the others.
Troubleshooting
| Symptom | Action |
|---|---|
Webhook never triggers |
Record, and compare the real request paths with your condition. |
Status always |
The condition does not match. Check the path, method and response code against real traffic. |
Target returns |
Check that |
Target returns |
Open the log entry and look at Transformation. The rendered output must match what the target API expects. |
Emails not delivered |
Check that the sender address is verified with Mailjet or SendGrid, check spam folders, and look at the provider’s delivery dashboard. |
Slack rejects the payload |
Check the Block Kit JSON at https://app.slack.com/block-kit-builder. |
Timeout errors |
Increase |
Related pages
-
Permissions: the
conditionpredicate language is shared with permission rules. -
The console: common behaviour of the pages (search, pagination, errors).
-
Free, Starter, Pro and Enterprise: what each plan includes.