Skip to content

Implement an external service for flow extensions

A flow extension calls an external HTTP service that you build and host. This service is where your custom logic runs. Use it to enrich user claims during registration, validate input against an external system, derive computed claims, or stop a flow with a business-rule failure.

This guide explains the contract your service must implement so that WSO2 Identity Server can integrate with it. You can build the service in any language or framework you prefer.

How it works

At the flow extension step of a flow, WSO2 Identity Server pauses the flow and sends an HTTP POST request with a JSON envelope to your service. Your service runs its logic and responds with an actionStatus that determines what happens next:

Status Meaning
SUCCESS Apply the returned operations and continue the flow.
FAILED Stop the flow for a business or policy reason (for example, "user not allowed"). WSO2 Identity Server surfaces this to the end user as a flow error.
ERROR Your service hit a processing or server-side error (a crash or a downstream failure). WSO2 Identity Server returns a 500 to the client.

Each request includes an allowedOperations whitelist that defines exactly which changes your service may make. If your response references anything outside this whitelist, WSO2 Identity Server rejects it.

Authentication

Configure the authentication scheme WSO2 Identity Server uses for the outbound call when you create the flow extension. Your service should validate the incoming credential on every request and reject unauthenticated calls.

Request reference

WSO2 Identity Server sends the following request to your service.

The request uses the following HTTP contract:

  • Method: POST
  • Content-Type: application/json
  • Timeout: 3 seconds

Top-level envelope

Field Type Description
actionType string Always FLOW_EXTENSION for this integration.
event object A snapshot of the flow state. See event object.
allowedOperations array The whitelist of changes your service may emit. See allowedOperations.
requestId string A unique identifier for this invocation.
{
  "actionType": "FLOW_EXTENSION",
  "event": { ... },
  "allowedOperations": [ ... ],
  "requestId": "93c2fb70-6f8c-444b-8ff8-36ff580dabb7"
}

Event object

The event object carries context about the flow and the organization.

Field Type Description
flow object The flow being executed. See flow object.
application object The application context. See application object.
tenant object The root organization context. See tenant object.
organization object The organization context. See organization object.

Note

Each field in the request appears only when you mark the corresponding attribute as Read in the extension's access configuration. Fields with empty values are omitted from the request.

Flow object

Field Type Description
flowType string The flow being executed.
flowId string The identifier for this single flow execution.
portalUrl string The URL of the WSO2 Identity Server portal page.
user object The user being acted on. See user object.
{
  "flowType": "REGISTRATION",
  "flowId": "d6e02342-7c5b-40de-a3f9-403ae5d163a9",
  "portalUrl": "https://accounts.asgardeo.io/t/<your-organization-name>/accounts/register",
  "user": { ... }
}

User object

Field Type Description
id string The user's unique identifier.
username string The username, without the user store domain prefix.
userStoreDomain string The user store the user belongs to. An empty string ("") denotes the primary user store.
claims array The current claims, each as { uri, value }.
credentials object The user's credentials, keyed by credential name (for example, password). See credentials.
{
   "id":"e5b1b0e8-0f3b-4d7a-9b6a-2f1c1a1b2c3d",
   "username":"john",
   "userStoreDomain":"",
   "claims":[
      {
         "uri":"http://wso2.org/claims/givenname",
         "value":"John"
      },
      {
         "uri":"http://wso2.org/claims/mobile",
         "value":"0123456789"
      }
   ],
   "credentials":{
      "password":{
         "type":"PLAIN_TEXT",
         "value":"<user's password>"
      }
   }
}
Credentials

Each credential value is a typed object:

Field Type Description
type string The format of the credential value. Currently PLAIN_TEXT.
value string The credential value.

If you mark a credential as encrypted, WSO2 Identity Server sends the JWE-encrypted form of the entire typed object instead. See work with encrypted values.

Warning

Credentials are highly sensitive. Only expose them to your endpoint when your use case requires it, and strongly consider marking them as encrypted.

Application object

Field Type Description
id string The application UUID.
{
  "id": "application-id"
}

Note

The application.id value is only available for flows initiated with the applicationId parameter.

Tenant object

Field Type Description
domain string The domain of the root organization.
{
  "domain": "example.com"
}

Organization object

Field Type Description
id string The organization UUID.
name string The organization name.
orgHandle string The organization handle.
depth integer The depth of the organization in the organization hierarchy.
{
  "id": "77084a9d-b745-4386-a44d-8dc0d44d0232",
  "name": "Example Organization",
  "orgHandle": "exampleorg",
  "depth": 0
}

Allowed operations

Each entry defines a change your service is permitted to make. If your response references anything outside this whitelist, WSO2 Identity Server rejects it.

FLOW_EXTENSION supports the replace operation, which creates or replaces a value at one of the listed paths:

  • If the value already exists in the flow, its value is replaced.
  • If no value exists in the flow, WSO2 Identity Server adds the given value.
  • If the supplied value is an empty string (""), the claim value is set to empty.

You may only target paths listed in paths.

{
  "op": "replace",
  "paths": [
    "/user/claims[uri=http://wso2.org/claims/identifier]",
    "/user/claims[uri=http://wso2.org/claims/tier]"
  ]
}

Work with encrypted values

When an attribute is marked as encrypted in the extension's access configuration, its value travels as a JWE compact string instead of plain text. WSO2 Identity Server uses RSA-OAEP-256 key encryption and A256GCM content encryption.

Encrypted values in the request

WSO2 Identity Server encrypts each Read attribute marked as encrypted using the encryption certificate uploaded for the extension. Your service decrypts these values with the private key of that certificate.

  • For claims and other string fields, the JWE payload is the field's string value.
  • For credentials, the JWE payload is the full typed credential object (for example, {"type": "PLAIN_TEXT", "value": "<secret>"}).
{
   "credentials":{
      "password":"eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2R0NNIiwiY3R5IjoiYXBwbGljYXRpb24vanNvbiJ9.<encrypted-key>.<iv>.<ciphertext>.<tag>"
   }
}

Note

If an attribute is marked as encrypted but the extension has no encryption certificate, WSO2 Identity Server omits that attribute from the request instead of sending it in plain text.

Encrypted values in the response

For each Write attribute marked as encrypted, your service must return the value as a JWE compact string encrypted with the public key of your WSO2 Identity Server organization. You can download the public certificate of your organization from the {{ api_base_path }}/api/server/v1/keystores/certs/public endpoint, which requires no authentication (see the Keystore Management API). WSO2 Identity Server decrypts the value with its private key before applying the operation. Returning a plain-text value, or a value that WSO2 Identity Server cannot decrypt, on an encrypted path aborts the flow.

For multi-valued claims on an encrypted path, join the values with commas, encrypt the joined string, and return it as a single-element array:

{
  "op": "replace",
  "path": "/user/claims[uri=http://wso2.org/claims/multiValuedClaim]",
  "value": ["<JWE of \"value1,value2\">"]
}

WSO2 Identity Server decrypts the string and splits it on commas to restore the individual values. Any other array shape on an encrypted multi-valued path aborts the flow.

Response reference

WSO2 Identity Server distinguishes outcomes by the HTTP status of your response:

  • HTTP 200 OK: Used for SUCCESS and FAILED responses. The actionStatus in the body tells WSO2 Identity Server which.
  • HTTP 400, 401, or 500: Interpreted as ERROR. When your service returns an error status or fails to respond entirely, WSO2 Identity Server treats it as a failure to execute the action and aborts the flow.

The response body has the following shape:

{
  "actionStatus": "SUCCESS" | "FAILED" | "ERROR",
  "operations": [ ... ]
}

Replace operation

The path must match one of the paths listed against the replace entry in allowedOperations. The same create or replace behavior applies: the value is created if absent and set to empty if value is empty.

{
  "op": "replace",
  "path": "/user/claims[uri=http://wso2.org/claims/customClaim]",
  "value": "new-value"
}

Besides claims, your service can also replace user credentials (for example, /user/credentials/password) when the corresponding path is marked as Write in the extension's access configuration.

Success response

Apply the returned operations and continue the flow. operations is optional; omit it when there is nothing to change but the flow should still proceed.

{
  "actionStatus": "SUCCESS",
  "operations": [
    {
      "op": "replace",
      "path": "/user/claims[uri=http://wso2.org/claims/customClaim]",
      "value": "new-value1"
    },
    {
      "op": "replace",
      "path": "/user/claims[uri=http://wso2.org/claims/multiValuedClaim]",
      "value": ["multi-value1", "multi-value2"]
    }
  ]
}

Failed response

Use FAILED when your service decides, on business or policy grounds, that the flow should not continue. For example, the user is on a deny list, or the supplied data failed an external check. The end user sees a flow-level error.

Field Type Description
actionStatus string FAILED
failureReason string A short, user-facing reason for the failure. Or send an i18n key.
failureDescription string A longer, user-facing description of the failure. Or send an i18n key.
{
  "actionStatus": "FAILED",
  "failureReason": "User not allowed",
  "failureDescription": "You are currently restricted from creating new accounts."
}

The end user sees the failureReason and failureDescription rendered as a flow error:

Flow extension failure shown to the end user

Error response

Use ERROR when your service itself hits a processing or server error (a downstream call failed, an unexpected exception was caught, and so on). Return HTTP 400, 401, or 500. WSO2 Identity Server treats this as a failure to execute the action and aborts the flow.

Field Type Description
actionStatus string ERROR
errorMessage string A short error message. Published to diagnostic logs.
errorDescription string A longer description of the error. Published to diagnostic logs.
{
  "actionStatus": "ERROR",
  "errorMessage": "External service failure",
  "errorDescription": "The external service met with an unexpected error."
}

Avoid exposing personal data in error messages

Don't include personally identifiable information (PII) in failureReason, failureDescription, errorMessage, or errorDescription. If you must include such data, mask it.

How operations are validated

WSO2 Identity Server validates every operation in a SUCCESS response before applying it. Validation problems fall into two groups.

Problems that abort the flow. Any of the following causes the whole response to be rejected and the flow to fail:

  • A value returned on an encrypted path isn't a string, isn't a valid JWE, or can't be decrypted.
  • An encrypted multi-valued claim isn't returned as a single-element array holding one JWE string.

Problems that skip the operation. The following cause WSO2 Identity Server to skip the offending operation and apply the rest, so a single invalid operation doesn't fail the user's flow:

  • The path is empty, unknown, or read-only (everything under /flow/).
  • The claim URI isn't in the http://wso2.org/claims/ dialect, refers to an identity claim (http://wso2.org/claims/identity/*), or doesn't resolve to an attribute registered in your organization.
  • The value is null or has the wrong type, for example, a plain string for a multi-valued claim, or an array for a single-valued claim.

Note

Single-valued claims take a string value, and multi-valued claims take an array of strings.

Example

The following example shows a self-registration flow where your service may replace two claims.

WSO2 Identity Server sends the following request:

{
   "actionType":"FLOW_EXTENSION",
   "event":{
      "flow":{
         "flowType":"REGISTRATION",
         "flowId":"d6e02342-7c5b-40de-a3f9-403ae5d163a9",
         "user":{
            "claims":[
               {
                  "uri":"http://wso2.org/claims/givenname",
                  "value":"John"
               },
               {
                  "uri":"http://wso2.org/claims/multiValuedClaim",
                  "value":[
                     "value1",
                     "value2"
                  ]
               },
               {
                  "uri":"http://wso2.org/claims/customClaim",
                  "value":"customValue1"
               }
            ]
         }
      }
   },
   "allowedOperations":[
      {
         "op":"replace",
         "paths":[
            "/user/claims[uri=http://wso2.org/claims/multiValuedClaim]",
            "/user/claims[uri=http://wso2.org/claims/customClaim]"
         ]
      }
   ],
   "requestId":"93c2fb70-6f8c-444b-8ff8-36ff580dabb7"
}

A response that applies a claim and continues the flow:

{
  "actionStatus": "SUCCESS",
  "operations": [
    {
      "op": "replace",
      "path": "/user/claims[uri=http://wso2.org/claims/customClaim]",
      "value": "123"
    }
  ]
}

A response that fails the flow:

{
  "actionStatus": "FAILED",
  "failureReason": "User not allowed",
  "failureDescription": "You are currently restricted from creating new accounts."
}

A response that reports a service-side error:

{
  "actionStatus": "ERROR",
  "errorMessage": "External service failure",
  "errorDescription": "The external service met with an unexpected error."
}

For the full API specification, see the flow extension API contract.

Next steps

Once your service is ready, configure the flow extension to register it as a connection and invoke it from a flow.