Ulabase

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.

The Webhooks page

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

  1. Open Service → Webhooks.

  2. 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

name

Yes

A name you recognise, such as "New User Slack Alert".

condition

Yes

Predicate expression. See Conditions below.

url

Yes

Destination URL. Must be reachable from the internet. Use https://httpbin.org/post for testing.

timeout_ms

No

Request timeout in milliseconds, from 1000 to 30000. Default: 5000.

headers

No

JSON object of headers sent with the request, for example for authentication.

transform.enabled

No

Boolean. When true, the Mustache template below shapes the request body.

transform.template

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

name

Yes

A name you recognise.

condition

Yes

Predicate expression.

email.to

Yes

Array of recipient email addresses.

email.cc

No

Array of CC email addresses.

email.subject

Yes

Subject line. Supports Mustache variables.

email.bodyType

Yes

"json" or "html".

email.bodyTemplate

With json

JSON template with Mustache variables. The result is sent as an HTML table.

email.htmlTemplate

With html

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

  1. Click Add webhook.

  2. Under Webhook type, choose HTTP Request or Email.

  3. Fill in the fields.

  4. 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

path('/users') and method('POST')

Fire on any write to /orders

path-prefix('/orders') and (method('POST') or method('PUT') or method('PATCH'))

Fire when a specific resource is deleted

path-template('/products/{id}') and method('DELETE')

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

{{service.id}}

Identifier of the Ulabase service.

{{timestamp}}

ISO 8601 timestamp of the event.

{{request.method}}

HTTP method of the triggering request (e.g. POST).

{{request.path}}

Path of the triggering request (e.g. /users).

{{request.query}}

Query string of the triggering request.

{{request.body.fieldName}}

Any field from the request body. Use dot notation for nested fields.

{{request.headers.Header-Name}}

Any header from the incoming request.

{{response.statusCode}}

HTTP status code of the response.

{{response.body.fieldName}}

Any field from the response body.

{{response.headers.Header-Name}}

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, failed or skipped

  • 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

  1. Go to https://api.slack.com/messaging/webhooks.

  2. Click Create New App → From scratch, name the app, and select your workspace.

  3. Open Incoming Webhooks, switch it ON, and click Add New Webhook to Workspace.

  4. 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

New User Slack Notification

URL

(your Slack webhook URL)

Condition

path('/users') and method('POST')

Timeout

5000

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

Order notification
{
  "text": "New Order",
  "blocks": [{
    "type": "section",
    "text": {
      "type": "mrkdwn",
      "text": ":shopping_cart: *Order #{{response.body._id}}*\nCustomer: {{request.body.customerName}}\nTotal: ${{request.body.total}}"
    }
  }]
}
5xx error alert
{
  "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

  1. Sign up at https://www.mailjet.com.

  2. Go to Account Settings → API Key Management and copy your API Key and Secret Key.

  3. Go to Sender Addresses and verify your sending email address.

  4. Base64-encode your credentials:

    echo -n "YOUR_API_KEY:YOUR_SECRET_KEY" | base64

Step 2: Configure it in Ulabase

Field Value

Name

Welcome Email via Mailjet

URL

https://api.mailjet.com/v3.1/send

Condition

path('/users') and method('POST')

Timeout

10000

Headers

{ "Authorization": "Basic <base64_credentials>", "Content-Type": "application/json" }

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

Order confirmation
{
  "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

https://api.sendgrid.com/v3/mail/send

Headers

{ "Authorization": "Bearer YOUR_SENDGRID_API_KEY", "Content-Type": "application/json" }

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/post first.

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 skipped

The condition does not match. Check the path, method and response code against real traffic.

Target returns 401

Check that headers carries the right credentials for the target API.

Target returns 400

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 timeout_ms, or check that the target answers promptly.