Skip to main content

Trigger custom logic and integrate with external systems with webhooks

Ory Actions supports webhooks, which are HTTP callbacks that can be triggered by specific events in your Ory-powered application. Webhooks allow Ory Actions to notify external systems, such as Hubspot, Mailchimp, when certain events occur, such as a user registration or profile update. Aside from integrating with external systems, webhooks allow you to use custom, external business logic in Ory Actions.

Should you use webhooks? They work perfectly for use cases like these:

Integration with third-party systems

If you want to integrate your Ory Actions-powered application with another system, such as a CRM, an email marketing platform, or an analytics tool, you can use webhooks to trigger updates in that system when events occur in your Ory Actions application.

Automated workflows

You can use webhooks to automate workflows and processes that span multiple systems. For example, you can use webhooks to automatically add a new lead to your CRM when a user registers in your application.

User data modification

You can use webhooks to modify user data when certain events occur. For example, you can use webhooks to update user data in your application when a user updates their profile information in another system.

Passing additional data on registration

When building your own UI you can pass additional data when completing the registration flow that gets forwarded to any configured webhooks. This can be used to solve more complex use cases that would be out of scope for identity traits.

tip

Always put security first! When using webhooks, ensure that the data you send is secure and that the external system you are integrating with is trustworthy. Additionally, make sure to consider the data privacy laws and regulations that may apply to your use case.

tip

To enhance your security measures while using the Ory Network, we've narrowed down the source IP addresses for our webhooks to these specific IPs:

  • 34.22.170.75
  • 35.242.228.133

These addresses will be used in the US region:

  • 35.245.69.134
  • 34.94.111.83

By setting your firewall rules to allow traffic only from these IP addresses, you'll be adding an extra layer of protection against any unwarranted access attempts.

Creating Actions on the Ory Network

The Ory Network Console offers a simple and intuitive tool to manage your Actions.

Follow the steps below:

  1. Navigate to Ory Network > Developers > Actions. There, click on + Create new Action.
  2. Choose the flow type that best suits your need and determine the execution time. Here, you should also provide other necessary data, such as the URL and request method.
  3. Click on Next. The ensuing screen allows you to establish authentication parameters and input request body details. To learn more about tailoring your request body, see the relevant section below.
  4. Once you have all adjusted settings, click on Save action. Your new Ory Action has now been created and is ready to operate.

Configuration overview

Ory Action webhooks have several configuration options:

hook: web_hook # To use webhooks, you must set 'hook' to 'web_hook'
config:
url: https://test.hook.site.sh/before_login_hook # Webhook URL.
method: POST # HTTP method used to send request to the webhook URL.
body: base64://ENCODED_JSONNET # Encoded Jsonnet template used to render payload.
response:
ignore: false # Defines if the webhook response should be ignored and run async. Boolean. OPTIONAL
parse: false # Defines if the webhook response should be parsed and interpreted. Boolean. OPTIONAL
auth:
type: # Can be one of 'basic_auth' or 'api_key'
config: # Additional auth config for the hook. Read next section for details.

Define HTTP request

Webhooks trigger HTTP requests to the webhook URL. You can configure the request method, URL, authorization, and body.

Customizing request body with Jsonnet

Webhooks bind the flow, as well as request headers (request_headers), request method (request_method), and the request URL (request_url) of the flow into the Jsonnet template for all methods and execution paths (before and after). For the after execution path of all flows, it binds the identity and the transient_payload object into the Jsonnet template as well. These objects are available through a ctx object.

To send { user_id: {some-id} } in the request body, create the following the Jsonnet template:

function(ctx) { user_id: ctx.identity.id }
Expand to see an example of all properties of the ctx object for a registration flow.
{
"ctx": {
"flow": {
"expires_at": "2023-01-31T12:19:35.782238Z",
"id": "cec1c06e-48eb-4f9d-abf1-2e287371f4eb",
"issued_at": "2023-01-31T11:19:35.782238Z",
"oauth2_login_challenge": null,
"request_url": "https://playground.projects.oryapis.com/self-service/registration/browser?return_to=",
"transient_payload": {
"custom_data": "test"
},
"type": "browser",
"ui": {
"action": "http://localhost:4455/self-service/registration?flow=cec1c06e-48eb-4f9d-abf1-2e287371f4eb",
"method": "POST",
"nodes": [
{
"attributes": {
"disabled": false,
"name": "csrf_token",
"node_type": "input",
"required": true,
"type": "hidden",
"value": "P91A1RzvL4xHAls2Gl76cbaXVMhBdpAj3c4vaRMckYY7JmGswmBHuul/+mZguOsQUOBmeJMOJWoa5xY2bd81CQ=="
},
"group": "default",
"messages": [],
"meta": {},
"type": "input"
},
{
"attributes": {
"autocomplete": "email",
"disabled": false,
"name": "traits.email",
"node_type": "input",
"required": true,
"type": "email"
},
"group": "password",
"messages": [],
"meta": {
"label": {
"id": 1070002,
"text": "Your E-Mail",
"type": "info"
}
},
"type": "input"
},
{
"attributes": {
"autocomplete": "new-password",
"disabled": false,
"name": "password",
"node_type": "input",
"required": true,
"type": "password"
},
"group": "password",
"messages": [],
"meta": {
"label": {
"id": 1070001,
"text": "Password",
"type": "info"
}
},
"type": "input"
},
{
"attributes": {
"disabled": false,
"name": "method",
"node_type": "input",
"type": "submit",
"value": "password"
},
"group": "password",
"messages": [],
"meta": {
"label": {
"context": {},
"id": 1040001,
"text": "Sign up",
"type": "info"
}
},
"type": "input"
}
]
}
},
"identity": {
"created_at": "0001-01-01T00:00:00Z",
"id": "3a5293f1-f4d6-49f4-b34f-6da62c360604",
"metadata_public": null,
"recovery_addresses": [
{
"created_at": "0001-01-01T00:00:00Z",
"id": "00000000-0000-0000-0000-000000000000",
"updated_at": "0001-01-01T00:00:00Z",
"value": "[email protected]",
"via": "email"
}
],
"schema_id": "default",
"schema_url": "",
"state": "active",
"state_changed_at": "2023-01-31T11:19:37.096674Z",
"traits": {
"email": "[email protected]"
},
"updated_at": "0001-01-01T00:00:00Z",
"verifiable_addresses": [
{
"created_at": "0001-01-01T00:00:00Z",
"id": "00000000-0000-0000-0000-000000000000",
"status": "pending",
"updated_at": "0001-01-01T00:00:00Z",
"value": "[email protected]",
"verified": false,
"via": "email"
}
]
},
"request_cookies": {
"__cypress.initial": "true",
"csrf_token_9eaa18484d26154fc69bb6b144100bec9a99a10709387620e8e4f0b7395e97ec": "BPshed6PaDaufaFQeuYRYeZ3MrDSeLVJxyk5X37DpI8="
},
"request_headers": {
"Accept": [
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
],
"Accept-Encoding": ["gzip"],
"Accept-Language": ["en-GB,en-US;q=0.9,en;q=0.8"],
"Cache-Control": ["max-age=0"],
"Connection": ["keep-alive"],
"Content-Length": ["180"],
"Content-Type": ["application/x-www-form-urlencoded"],
"Cookie": [
"csrf_token_9eaa18484d26154fc69bb6b144100bec9a99a10709387620e8e4f0b7395e97ec=BPshed6PaDaufaFQeuYRYeZ3MrDSeLVJxyk5X37DpI8=; __cypress.initial=true"
],
"Origin": ["http://localhost:4455"],
"Proxy-Connection": ["keep-alive"],
"Referer": ["http://localhost:4455/registration?flow=cec1c06e-48eb-4f9d-abf1-2e287371f4eb"],
"Sec-Ch-Ua": ["\"Not_A Brand\";v=\"99\", \"Google Chrome\";v=\"109\", \"Chromium\";v=\"109\""],
"Sec-Ch-Ua-Mobile": ["?0"],
"Sec-Ch-Ua-Platform": ["\"macOS\""],
"Sec-Fetch-Dest": ["iframe"],
"Sec-Fetch-Mode": ["navigate"],
"Sec-Fetch-Site": ["same-origin"],
"Upgrade-Insecure-Requests": ["1"],
"User-Agent": [
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36"
]
},
"request_method": "POST",
"request_url": "https://playground.projects.oryapis.com/self-service/registration?flow=cec1c06e-48eb-4f9d-abf1-2e287371f4eb"
}
}

Use the ctx object to access these fields in your Jsonnet template, for example:

./segment_identify.jsonnet
function(ctx) {
userId: ctx.identity.id,
customData: ctx.transient_payload.custom_data
traits: {
email: ctx.identity.traits.email,
name: ctx.identity.traits.name,
newsletterConsent: ctx.identity.traits.consent.newsletter,
},
}

Request authentication

Webhooks can be configured to use HTTP Basic Authentication or API Key authentication.

  • API key authentication. Type must be set to api_key. All properties are mandatory.

    hook: web_hook
    # other configuration keys
    config:
    auth:
    type: api_key
    config:
    name: Authorization
    value: { API Key value }
    in: header # alternatively "cookie"
  • "Basic Authentication" type authentication. Type must be set to basic_auth. All properties are mandatory. For basic_auth the config looks as follows:

    hook: web_hook
    # other configuration keys
    config:
    auth:
    type: basic_auth
    config:
    user: { USERNAME }
    password: { YOUR-PASSWORD }

Canceling webhooks

You can cancel configured webhooks and not make the defined request.

This can come in handy in testing. For example, by canceling a webhook, you prevent the test accounts created during testing from being added to CRM.

To get this behavior, cancel the webhook by raising an error for all accounts with the test- name prefix:

function(ctx)
if std.startsWith(ctx.identity.traits.email, "test-") then
error "cancel"
else
{
user_id: ctx.identity.id
}
info

Currently, Jsonnet doesn't support regular expressions. Follow this issue to see if the feature has been implemented: google/go-jsonnet/409.

Webhook response handling

Webhooks can be configured to ignore the response, or to parse the response and use it to interrupt the flow or to update the identity.

Non-blocking / async webhooks

Sometimes you need to interact with external systems without needing their response. For example, when a user signs up, a webhook is that adds their email address to the newsletter is triggered.

If you decide that the system response that indicates if the process was successful is not critical for your setup, make the webhook execution non-blocking to ignore whatever response is returned.

To do that, set response.ignore to true in the webhook config:

hook: web_hook
config:
response:
ignore: true

Modify identities

If you want the result of webhook execution to modify the identity, you can configure the webhook to parse the response:

hook: web_hook
config:
response:
parse: true

When the webhook target returns a 200 OK response code and the response body is a JSON object with the key identity in it, the values from that object will be used to change the identity before it is saved to the database.

info

Modifying the identity is currently only possible during the registration and settings flows.

When updating any of the identity fields, be aware that the whole field is replaced by the value returned by the webhook. For example, if the user signed up with

Identity before hook
{
"identity": {
"traits": {
"email": "[email protected]"
}
}
}

and the webhook returns

Incorrect webhook response
{
"identity": {
"traits": {
"another_value": "example"
}
}
}

the identity will no longer have the email trait, which will break the sign up flow. Always make sure that the webhook returns complete data:

Correct webhook response
{
"identity": {
"traits": {
"email": "[email protected]",
"another_value": "example"
}
}
}

Update identity traits

When the webhook target returns a 200 OK response code and the response body is a JSON object with the key identity.traits in it, the values from that object will be used to replace the identity traits before they are saved to the database.

info

This method replaces the entire identity traits object. It is not possible to update only a single trait.

{
"identity": {
"traits": {
"email": "[email protected]",
"the_webhook": "updated this value"
}
}
}

Update identity metadata

When the webhook target returns a 200 OK response code and the response body is a JSON object with the key identity.metadata_public or identity.metadata_admin in it, the values from that object will be used to replace the identity metadata fields before they are saved to the database.

info

This method replaces the entire metadata object. You can't update only a single value in metadata_public or metadata_admin.

{
"identity": {
"metadata_public": {
"the_webhook": "changed this value",
"and_added_this_one": "too"
},
"metadata_admin": {
"the_webhook": "updated this value",
"and_this_one": "too"
}
}
}

Update verification or recovery addresses

When the webhook target returns a 200 OK response code and the response body is a JSON object with the key identity.verifiable_addresses in it, the values from that object will be used to replace the identity verification addresses before they are saved to the database.

info

Verification and recovery addresses are taken from the identity.traits object using the Identity Schema. If you add additional verification or recovery addresses, these will be deleted unless the Identity traits also contain that address.

{
identity: {
traits: {
email: "[email protected]",
},
verifiable_addresses: [
{
status: "completed",
value: "[email protected]",
verified: true,
via: "email",
},
],
recovery_addresses: [
{
value: "[email protected]",
via: "email",
},
],
},
}

Flow-interrupting webhooks

If you want the result of webhook execution to have the potential of interrupting the flow, you can configure the webhook to parse the response:

hook: web_hook
config:
response:
parse: true

The external service called in the flow decides if it allows the flow to continue, or if it interrupts the flow. For example:

  • For HTTP response codes 1xx, 2xx, and 3xx, the flow is not interrupted.
  • For HTTP response codes 4xx, 5xx, the external service interrupts the flow and returns a predefined payload.

Example payload:

{
"messages": [
{
"instance_ptr": "#/traits/foo/bar", # Points to the field that failed validation.
"messages": [
{
"id": 123, # Unique numeric ID of the error that helps the frontend to interpret this message.
"text": "field must be longer than 12 characters",
"type": "validation",
"context": {
"value": "short value"
}
}
]
}
]
}

When using an after registration hook, you can define the specific point in the flow where this webhook is called:

  • When the webhook parses the response, the logic is called before the system creates a new identity.
  • When the webhook does not parse the response, the logic is called after the system creates a new identity.

You can call a webhook before and after the system creates an identity. To do that, define two webhooks with different parse values:

- hook: web_hook
config:
response:
parse: true
- hook: web_hook
config:
response:
parse: false
tip

Flow-interrupting webhooks work best if you give users meaningful information about the status of their flow. The best place to trigger these hooks is after flows.

Webhook retries

Sometimes webhook delivery can fail due to issues such as network problems or server errors. To mitigate this, Ory Network implements a retry policy that attempts to deliver the payload up to three times, with a 30-second timeout between each attempt. This means that if delivery fails on the first attempt, Ory Network will automatically retry the delivery two more times, with a 30-second delay between each retry.