OutSystems REST API callbacks (OnBeforeRequest and OnAfterResponse) are action hooks that run before every outgoing request and after every incoming response for a consumed REST API. Use OnBeforeRequest to inject dynamic headers (Authorization, correlation IDs, tenant identifiers) and modify URLs. Use OnAfterResponse to inspect HTTP status codes, transform response payloads, raise typed exceptions for error status codes, and log response metadata. Both callbacks apply to all methods in the REST API node.
The REST API Callback Pipeline in OutSystems
When OutSystems calls an external REST API, the request goes through a two-stage pipeline: OnBeforeRequest (outgoing) and OnAfterResponse (incoming). These callbacks let you intercept and modify every HTTP interaction at the transport level, regardless of which specific method is being called. This is the correct place to handle cross-cutting concerns like authentication header injection, request tracing, response normalization, and HTTP error detection. Without callbacks, each method call would need its own header injection logic — callbacks centralize it once.
Prerequisites
- A consumed REST API already imported under Logic tab → Integrations → REST with at least one working method
- Site Properties or Secret Settings configured for any credentials you plan to inject
- Understanding of OutSystems exception types (User Exception, Communication Exception, Security Exception)
- For advanced callbacks: Integration Studio installed (O11) or External Libraries SDK familiarity (ODC)
Step-by-step guide
Understand the two callback types: simple vs advanced
Understand the two callback types: simple vs advanced
OutSystems provides two versions of each callback: Simple callbacks (OnBeforeRequest, OnAfterResponse): - Implemented as regular OutSystems Server Action logic - Work with the HTTPRequest/HTTPResponse OutSystems structures - Can read/write headers, URL, status code, response body (as Text) - Available to all developers, no additional tooling - Limitation: cannot access the raw binary request/response stream Advanced callbacks (OnBeforeRequestAdvanced, OnAfterResponseAdvanced): - Implemented in C# code via Integration Studio (O11) or External Libraries (ODC) - Have direct access to the .NET HttpRequestMessage and HttpResponseMessage objects - Required for: binary payload manipulation, streaming, custom TLS, non-standard serialization - Most integrations only need the simple callbacks To add a simple callback, click your REST API node → Properties panel → On Before Request → select (New OnBeforeRequest). For advanced, select (New OnBeforeRequestAdvanced).
Expected result: You understand when to use simple vs advanced callbacks and have chosen the appropriate type for your use case.
Add OnBeforeRequest to inject a dynamic Bearer token
Add OnBeforeRequest to inject a dynamic Bearer token
Click your consumed REST API node. In the Properties panel, click On Before Request → (New OnBeforeRequest). Service Studio creates an OnBeforeRequest action inside your REST API node. Open it. The action has two pre-defined parameters: - Request (HTTPRequest, input) — the current request before sending - CustomizedRequest (HTTPRequest, output) — the modified request to send You must assign CustomizedRequest at the end, even if you make no changes. Build this flow: Start → GetAccessToken [Server Action that returns current valid token — see oauth tutorial] → Assign NewAuthHeader NewAuthHeader.Name = "Authorization" NewAuthHeader.Value = "Bearer " + GetAccessToken.Token → ListAppend (Request.Headers, NewAuthHeader) → Assign CustomizedRequest = Request → End IMPORTANT: Use ListAppend on Request.Headers — do NOT replace the entire Headers list. Replacing it removes all pre-existing headers including Content-Type.
Expected result: Every request sent through this REST API now includes the Authorization header. Verify in Service Center → Monitoring → Integrations that the header appears in the logged request.
Conditionally modify the URL in OnBeforeRequest
Conditionally modify the URL in OnBeforeRequest
OnBeforeRequest can also modify the request URL. This is useful for: - Appending environment-specific query parameters - Routing to different API versions based on a feature flag - Injecting a tenant identifier into the URL path URL modification example for multi-tenant API: Start → GetSiteProperty (TenantId) → Assign Request.URL = Replace(Request.URL, "/api/", "/api/" + TenantId + "/") → Assign CustomizedRequest = Request → End For query parameter appending: Start → GetSiteProperty (ApiVersion) → Assign Request.URL = Request.URL + "?api-version=" + ApiVersion → Assign CustomizedRequest = Request → End Note: If the URL already contains query parameters, use "&api-version=" + ApiVersion instead of "?api-version=" to avoid malforming the URL. Check using the Index() expression function: If(Index(Request.URL, "?", 0, False, False) >= 0, "&", "?")
Expected result: The Request.URL is modified before sending. Log entries in Service Center show the modified URL.
Implement OnAfterResponse to handle HTTP error status codes
Implement OnAfterResponse to handle HTTP error status codes
Click your REST API node. In Properties panel, set On After Response → (New OnAfterResponse). The action parameters: - Response (HTTPResponse, input) — the received response with StatusCode (Integer), Headers (List), ResponseText (Text) - CustomizedResponse (HTTPResponse, output) — the modified response to return to the caller Build HTTP status code handling: Start → Switch Case: Response.StatusCode = 200 OR Response.StatusCode = 201 → [pass through] Case: Response.StatusCode = 401 → Raise Exception (TokenExpired, User Exception) with message "Authentication token expired. Please re-authenticate." Case: Response.StatusCode = 403 → Raise Exception (InsufficientPermissions, Security Exception) Case: Response.StatusCode = 404 → Raise Exception (ResourceNotFound, User Exception) Case: Response.StatusCode = 429 → GetResponseHeader ("Retry-After") → Assign RetryAfterSeconds → Raise Exception (RateLimitExceeded, User Exception) with message "Rate limit exceeded. Retry after " + RetryAfterSeconds + " seconds." Case: Response.StatusCode >= 500 → Raise Exception (ExternalServerError, Communication Exception) Default → [Assign CustomizedResponse = Response → End] Create named User Exceptions first via Logic tab → Exceptions → right-click → Add Exception.
Expected result: HTTP 4xx and 5xx responses raise typed exceptions that can be individually caught in calling Server Actions. 2xx responses pass through normally.
Extract and use response headers in OnAfterResponse
Extract and use response headers in OnAfterResponse
Some APIs return important metadata in response headers (pagination links, rate limit remaining, request IDs, correlation IDs). Access them in OnAfterResponse using the GetResponseHeader action from HTTPRequestHandler. First, add the HTTPRequestHandler reference: Module → Manage Dependencies (Ctrl+Q) → search HTTPRequestHandler → check GetResponseHeader → Apply. Inside OnAfterResponse: Start → GetResponseHeader (Name = "X-Request-Id") → Assign RequestTrackingId = GetResponseHeader.Value → GetResponseHeader (Name = "X-RateLimit-Remaining") → If (TextToInteger(GetResponseHeader.Value) < 10) → [True] LogWarning (Module = "MyModule", Message = "API rate limit nearly exhausted: " + GetResponseHeader.Value + " remaining") → [False] pass → Assign CustomizedResponse = Response → End For headers that must be passed to the calling action, store them in a Site Property or a short-lived cache entity. Callbacks cannot directly return values to the calling method — they only modify the request/response objects.
Expected result: Response header values are inspected and acted upon. Rate limit warnings appear in Service Center logs before hitting the limit.
Test and debug callbacks with the Service Studio debugger
Test and debug callbacks with the Service Studio debugger
Callbacks run as part of the server-side flow and are fully debuggable. To step through OnBeforeRequest: 1. Open the OnBeforeRequest action in the Logic tab 2. Click any node to set a breakpoint (the node gets a red dot) 3. Press F6 (Debug) in Service Studio 4. Trigger an action from the screen that calls the REST API method 5. The Debugger tab (lower pane) pauses at your breakpoint 6. Use the Scope panels to inspect Request.Headers, URL, and any local variables 7. Press F8 (Step Over) to advance through each node Common callback bugs found via debugging: - CustomizedRequest not assigned at End → outgoing request is null, causing an error - Headers being replaced (= assignment) instead of appended (ListAppend) → Content-Type header lost - Switch Case order wrong → 200 OK caught by a >= 500 default
Expected result: You can step through the callback logic and inspect the HTTPRequest/HTTPResponse structures to verify modifications are correct.
Complete working example
1/* OnBeforeRequest — Multi-header injection pattern2 Injects Bearer token, correlation ID, and API version header3 Input: Request (HTTPRequest)4 Output: CustomizedRequest (HTTPRequest)5*/67Start8 |9 v10[GetOrRefreshToken] -- Server Action (see oauth-authentication tutorial)11 Returns: AccessToken (Text)12 |13 v14[Assign] AuthHeader15 AuthHeader.Name = "Authorization"16 AuthHeader.Value = "Bearer " + AccessToken17 |18 v19[ListAppend] Request.Headers += AuthHeader20 |21 v22[Assign] CorrelationHeader23 CorrelationHeader.Name = "X-Correlation-Id"24 CorrelationHeader.Value = GenerateGuid()25 |26 v27[ListAppend] Request.Headers += CorrelationHeader28 |29 v30[Assign] VersionHeader31 VersionHeader.Name = "X-API-Version"32 VersionHeader.Value = Site.ApiVersion33 |34 v35[ListAppend] Request.Headers += VersionHeader36 |37 v38[Assign] CustomizedRequest = Request39 |40 v41End424344/* OnAfterResponse — Status code handling + rate limit monitoring45 Input: Response (HTTPResponse)46 Output: CustomizedResponse (HTTPResponse)47*/4849Start50 |51 v52[Switch on Response.StatusCode]53 |54 Case = 401 -->55 [Assign] NeedsTokenRefresh = True (update Site Property)56 [Raise Exception: TokenExpired (User Exception)]57 |58 Case = 403 -->59 [Raise Exception: AccessDenied (Security Exception)]60 |61 Case = 404 -->62 [Raise Exception: ResourceNotFound (User Exception)]63 |64 Case = 429 -->65 [GetResponseHeader "Retry-After"] RetryAfterSecs66 [Raise Exception: RateLimitExceeded (User Exception)67 Message = "Rate limited. Retry after " + RetryAfterSecs + "s"]68 |69 Case >= 500 -->70 [LogError]71 Module = "IntegrationModule"72 Message = "External API server error: " + IntegerToText(Response.StatusCode)73 Detail = Response.ResponseText74 [Raise Exception: ExternalServerError (Communication Exception)]75 |76 Default (2xx) -->77 [GetResponseHeader "X-RateLimit-Remaining"] RateLimitRemaining78 [If TextToInteger(RateLimitRemaining) < 10]79 [True] [LogWarning Message = "Rate limit low: " + RateLimitRemaining]80 [False] (continue)81 [Assign] CustomizedResponse = Response82 |83 v84EndCommon mistakes
Why it's a problem: Replacing Request.Headers entirely instead of appending to it
How to avoid: Use ListAppend(Request.Headers, NewHeader) to add headers. Assigning a new list to Request.Headers removes all headers previously set by OutSystems, including Content-Type: application/json and Accept headers, breaking the API call.
Why it's a problem: Forgetting to assign CustomizedRequest = Request at the end of OnBeforeRequest
How to avoid: CustomizedRequest is a required output parameter. If not assigned, OutSystems cannot proceed with the request. TrueChange will highlight this as a warning — do not ignore it. Every code path through OnBeforeRequest must assign CustomizedRequest before reaching End.
Why it's a problem: Calling a database Aggregate inside OnBeforeRequest on every single API call
How to avoid: OnBeforeRequest runs synchronously before every HTTP call. A database query here adds database round-trip latency to every API call. Cache frequently-needed values (tokens, config) in Site Properties and only hit the database when the cached value is stale.
Why it's a problem: Using OnBeforeRequest for logic that only applies to one specific method rather than all methods
How to avoid: OnBeforeRequest applies to ALL methods in the REST API node. If the logic should only apply to one method, implement it directly in the Server Action that calls that method, not in the callback. Check Request.URL in the callback if you absolutely must differentiate.
Best practices
- Always assign CustomizedRequest at the end of OnBeforeRequest, even in exception paths — failing to do so raises a runtime error on every API call.
- Use ListAppend to add headers, not assignment — replacing the entire Headers list removes Content-Type, Accept, and other headers set by OutSystems automatically.
- Keep callback logic stateless where possible — avoid reading from the database in OnBeforeRequest to keep latency low. Cache authentication tokens in Site Properties instead.
- Centralize all cross-cutting HTTP concerns in callbacks (auth, correlation IDs, logging) rather than repeating the logic in each Server Action that calls the API.
- For APIs with multiple REST nodes in the same module, consider creating a reusable Server Action for the shared callback logic and calling it from each node's callback.
- Test callbacks by setting breakpoints inside the OnBeforeRequest/OnAfterResponse actions in the Service Studio Debugger — they participate fully in the debug session.
- Use the OnAfterResponse callback to transform malformed response bodies (e.g., non-standard date formats) into normalized OutSystems-compatible formats before the generated structure deserialization occurs.
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I'm building an OutSystems integration with a SaaS API that uses rotating API keys. Each API key expires after 24 hours, and I get the new key from a key management endpoint. Help me design the OnBeforeRequest and OnAfterResponse callbacks to: 1) check if the cached API key is expiring within 1 hour, fetch a new one if needed, and inject it as an X-API-Key header, 2) handle 401 responses by triggering a key refresh and raising an exception for the caller to retry.
I have a consumed REST API in OutSystems called PaymentGatewayAPI with methods: CreatePayment, GetPayment, RefundPayment. The API requires: 1) Authorization: Bearer {token} header (token changes every 30 min), 2) X-Idempotency-Key: {guid} header on POST calls only, 3) Any 402 response should raise a PaymentDeclined User Exception with the response body as the message. Write the complete OnBeforeRequest and OnAfterResponse action flows using arrow notation.
Frequently asked questions
Can I have both OnBeforeRequest and OnAfterResponse active on the same REST API node simultaneously?
Yes. Both callbacks are independent and both run on every request/response cycle. OnBeforeRequest fires before the HTTP call is made; OnAfterResponse fires after the response is received. They do not interfere with each other. You can and should use both together for complete request/response control.
How do I access the response body as a text string in OnAfterResponse to parse error messages from APIs that return non-standard error formats?
The Response.ResponseText attribute on the HTTPResponse input parameter contains the full response body as a Text string. You can use JSON parsing logic (or the JSONDeserialize built-in) to extract specific fields from non-standard error responses. After extracting the error detail, include it in the message of the raised exception so calling Server Actions can surface the specific error to users.
Do callbacks apply to REST API calls made from ODC apps the same way as O11?
Yes. The simple callback mechanism (OnBeforeRequest, OnAfterResponse) works identically in ODC Studio. The difference is advanced callbacks: in O11 they use Integration Studio (.xif extensions in C#); in ODC they use External Libraries (C# .NET 8 SDK, deployed as ZIP packages via ODC Portal). For most use cases, simple callbacks are sufficient in both platforms.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation