Back to documentation

Adal Outbound

Send outbound HTTP requests through your selected Adal region with queuing, automatic retries, and delivery history.

View as Markdown
On this page

Adal Outbound

Adal Outbound is a dedicated service for outbound HTTP delivery. Your application provides the recipient URL, HTTP method, headers, and request body. Adal queues the message, delivers it, retries failed attempts, and records the results.

Your application → Adal Outbound → Recipient URL

Outbound operates independently of Adal's inbound webhook flow. You do not need to create a Server, Request, or Destination: your application uses a separate API and specifies the recipient for each message.

Beta: Adal Outbound is under active development. Before using it in production, test your integration with a test recipient and make sure it can handle duplicate deliveries.

When to use Outbound

Outbound is designed for applications that send webhooks or other HTTP events to external systems without maintaining their own delivery queue, retry scheduler, and delivery log.

For example:

  • a SaaS product sends events to customer webhook URLs;

  • a platform notifies partners about changes to orders, payments, or other resources;

  • an internal service calls the public API of a CRM, help desk, or another external system;

  • a team needs a single delivery history with diagnostic data;

  • different workloads need an explicit choice of delivery region and history storage location.

Each request to Outbound creates one message for one recipient.

How Outbound works

The workflow consists of five steps:

  1. Create a refresh token under Outbound → Tokens.

  2. Exchange the refresh token for an access token through the Control Plane.

  3. Retrieve the list of regions, select one, and save its domain.

  4. Send messages directly to the selected region.

  5. When the access token expires, obtain a new one using the same refresh token.

Outbound → Tokens │ ▼ Refresh token │ ▼ POST /auth/outbound │ ▼ Access token │ ├──► GET /outbound/servers │ └──► POST https://{region-domain}/api/send

You do not need to retrieve the region list every time you renew an access token. Retrieve it during initial setup, save the selected domain, and refresh the list separately when you want to check for new regions or change the delivery location.

Adal does not select a region automatically or perform cross-region failover. Your application determines which region to use.

Authentication

Outbound uses two types of tokens.

Token Purpose Where to use it
Refresh token Obtain an access token Control Plane only
Access token Retrieve regions and send messages Control Plane and the selected region

Refresh token

A refresh token is a long-lived application secret. Create one under Outbound → Tokens in the dashboard.

The refresh token is displayed only when it is created. Save it immediately: you cannot view or recover its value later. If you lose the token, create a new one.

You can revoke an unused or compromised refresh token from the same dashboard section. Once revoked, the token can no longer be used to obtain an access token.

Store the refresh token in your application's secret storage. Do not include it in source code, client-side applications, ordinary logs, or error messages.

Obtaining an access token

Before using the Outbound API, exchange the refresh token for a short-lived access token:

POST https://cp.adal.cloud/auth/outbound Authorization: Bearer <refresh-token>

A successful response contains the access token and its expiration details:

{ "access_token": "<access-token>", "expires_at": 1786200278343 }

Use the access token to retrieve the region list and send messages.

When the access token expires, repeat the request to /auth/outbound with the same valid refresh token. You do not need to retrieve the region list again simply because the access token was renewed.

Do not send the refresh token to regional APIs or use it to send messages.

Selecting a region

Retrieve the list of available regions through the Control Plane:

GET https://cp.adal.cloud/outbound/servers Authorization: Bearer <access-token>

Example list item:

{ "key": "kz2", "name": "Almaty, Kazakhstan", "domain": "kz2.adal.cloud", "country": "Kazakhstan", "city": "Almaty" }
Field Description
key Region identifier
name Display name
domain Regional API domain
country Host country
city Host city

Use the domain value returned by the API when sending messages. Do not construct the regional domain yourself.

For example:

https://kz2.adal.cloud

If the selected region is unavailable, Adal does not automatically move the message to another region. Your application is responsible for deciding whether to change regions and accounting for the data-location implications.

Sending a message

Send the message directly to the selected region:

POST https://kz2.adal.cloud/api/send Authorization: Bearer <access-token> Content-Type: application/json

Example request body:

{ "destination": "https://example.com/webhooks", "method": "POST", "headers": { "Content-Type": ["application/json"], "X-Event-Id": ["evt_01K2..."] }, "body_base64": "eyJldmVudCI6Im9yZGVyLmNyZWF0ZWQifQ==", "max_attempts": 3, "add_idempotency": true }

Message fields

Field Required Description
destination yes Public HTTP or HTTPS recipient URL
method yes HTTP method for the outgoing request
headers no Outgoing request headers
body_base64 no Base64-encoded outgoing request body
max_attempts no Maximum number of delivery attempts for the message
add_idempotency no Whether Adal should generate an Idempotency-Key when one is not supplied; defaults to true

Each destination must identify a single recipient. If you need to send the same event to multiple systems, create a separate message for each recipient.

Headers

Each header name maps to an array of values:

{ "X-Event-Id": ["evt_01K2..."], "X-Example": ["one", "two"] }

This format allows you to provide multiple values for the same HTTP header.

Adal manages certain transport headers. Do not set Host, Content-Length, Connection, Transfer-Encoding, or other hop-by-hop or proxy headers manually.

If the API rejects a header as reserved, remove it from headers.

You may include your own stable event identifier, such as X-Event-Id or Idempotency-Key. Adal preserves user-supplied headers across retry attempts.

Automatic Idempotency-Key

By default, Adal adds an automatically generated Idempotency-Key to the outgoing HTTP request when you do not provide one. Use the optional add_idempotency field to control this behavior:

Field Type Required Default
add_idempotency boolean no true

The value must be the JSON boolean true or false. Strings and numbers such as "true", 1, and 0 are not accepted.

add_idempotency User-supplied Idempotency-Key Result
omitted absent Adal generates a UUIDv7 key
omitted present Adal keeps the user-supplied key
true absent Adal generates a UUIDv7 key
true present Adal keeps the user-supplied key
false absent The header is not added
false present Adal keeps the user-supplied key

false disables only automatic generation. Adal never replaces or removes a user-supplied Idempotency-Key.

An automatically generated key uses the UUIDv7 format:

Idempotency-Key: 01989d55-20df-7a72-87f9-6e50f3d78315

Adal generates the key and stores it with the message before queueing the delivery. Every attempt for that message uses the same value, including retries. A separate call to /send without a user-supplied key receives a new UUIDv7.

To use your own logical key, include it in headers:

{ "destination": "https://example.com/hook", "method": "POST", "headers": { "Content-Type": ["application/json"], "Idempotency-Key": ["order-create-12345"] }, "body_base64": "eyJvcmRlcl9pZCI6MTIzNDV9", "add_idempotency": true }

The outgoing request contains Idempotency-Key: order-create-12345 unchanged. This is useful when the calling application needs to reuse one logical key across multiple /send calls. The same user-supplied key is preserved even when add_idempotency is false.

To disable automatic generation when no key is supplied:

{ "destination": "https://example.com/hook", "method": "POST", "add_idempotency": false }

In this case, Adal does not add an Idempotency-Key.

Request body

body_base64 contains the outgoing HTTP request body encoded as Base64. This allows JSON, text, and binary data to use the same message format.

For example, this body:

{"event":"order.created"}

is sent as:

{ "body_base64": "eyJldmVudCI6Im9yZGVyLmNyZWF0ZWQifQ==" }

Base64 does not specify a media type. If required, provide Content-Type separately in headers.

Send response

After accepting the message, Adal returns:

202 Accepted

The response contains the message ID, selected region, and initial pending status. For example:

{ "id": 123, "region": "kz2", "status": "pending" }

202 Accepted only means that Adal accepted the message and queued it for delivery.

It does not mean that:

  • the recipient has already received the request;

  • the recipient returned a successful HTTP response;

  • the recipient's business operation completed successfully.

After accepting the message, Adal processes it asynchronously and records the result of each delivery attempt.

Message statuses

Outbound delivery history uses the following primary statuses:

Status Meaning
pending The message is waiting for its first or next attempt
delivering Adal is sending the HTTP request to the recipient
success The recipient returned a 2xx HTTP status
failed All permitted attempts were exhausted without a 2xx response

These statuses apply only to Outbound and are unrelated to delivery statuses in Adal's inbound flow.

Delivery and retries

Delivery is considered successful when the recipient returns a 2xx HTTP status.

A connection, DNS, or TLS error, a timeout, or any other HTTP status counts as a failed attempt. As long as the max_attempts limit has not been reached, Adal schedules another attempt.

The delay after failed attempt number N is calculated as follows:

delay = N² minutes

For example:

Failed attempt Delay before the next attempt
1 1 minute
2 4 minutes
3 9 minutes

A subsequent attempt is made only when the limit configured for the message permits another attempt.

Automatic retries do not consume additional credits. Credits are charged once, when Adal accepts the message for delivery.

At-least-once delivery and idempotency

Outbound uses an at-least-once delivery model. Under some failure conditions, the same request may be delivered to the recipient more than once.

For example, the recipient may process the request successfully, but its response may be lost in transit. Adal cannot reliably distinguish that situation from one where the request was not processed, so it performs another attempt.

The recipient should therefore:

  • process requests idempotently where possible;

  • identify duplicates using a stable event ID or idempotency key;

  • store the processing result for that identifier;

  • avoid creating a duplicate payment, order, or notification when a request is delivered again.

Adal preserves the supplied headers and message body across attempts, so the event identifier remains unchanged. When Adal generates an Idempotency-Key, it also uses the same generated value for every attempt of the message. This helps the recipient recognize a retry, but the recipient must still implement idempotency or deduplication for the key to prevent a repeated business operation.

A 2xx response means that the recipient successfully responded to the HTTP request. It does not prove that the recipient's internal business operation completed successfully.

Recipient URL security

Outbound sends requests to a user-provided URL, so Adal validates destination and protects its infrastructure against SSRF.

Only publicly accessible HTTP and HTTPS addresses are supported. Adal rejects:

  • localhost and loopback addresses;

  • private and reserved IP ranges;

  • internal hostnames;

  • URLs containing embedded usernames or passwords.

The address is validated before the connection is opened and again after an HTTP redirect. As a result, Outbound cannot deliver requests directly to 127.0.0.1, a private IP address, or an internal hostname.

Examples of invalid addresses:

http://localhost/ http://127.0.0.1/ http://192.168.1.10/

Adal's protections do not replace input validation on the recipient side. The recipient must verify the sender's signature or other authentication mechanism, validate the request data, and limit the permissions of any credentials it uses.

History and diagnostics

The current message status and delivery-attempt history are available in the dashboard:

Outbound → Messages

First select the region through which the message was sent.

Depending on the result, Adal displays the following information for each attempt:

  • HTTP response status and headers;

  • technical error details;

  • DNS resolution time;

  • connection and TLS setup time;

  • TTFB and total request time;

  • TLS connection details;

  • the HTTP redirect chain and final URL.

This data helps distinguish a DNS failure from a TLS problem, timeout, slow application response, or unsuccessful HTTP status.

Adal does not store or display the recipient's HTTP response body.

Message and attempt history is available through the dashboard, not through the public API.

Regional processing and storage

Each message is sent directly through the selected region. The data required for delivery and diagnostics is stored in the same region, including:

  • recipient URL;

  • HTTP method;

  • headers;

  • message body;

  • current status;

  • attempt history.

The Control Plane is used for authentication and region discovery, but it is not a centralized store for Outbound history.

Region selection may matter for network routing, processing location, and your organization's internal data-location requirements.

Selecting a region alone does not establish compliance with a particular law or industry standard. That assessment depends on the data involved, applicable agreements, and relevant requirements.

Adal does not perform automatic cross-region failover. If your application switches to another region, the history of older and newer messages will be stored in different regions.

Credits

Credits are charged when Adal successfully accepts a message, not for each delivery attempt.

Retries performed for an already accepted message do not consume additional credits.

An automatically generated Idempotency-Key is a service header. It is not included in max_header_count, max_headers_bytes, maximum request-size validation, or the credit calculation. Automatic generation therefore cannot increase the message cost or cause the message to exceed user header limits.

A user-supplied Idempotency-Key is treated like any other user header and is included in header limits, request-size validation, and the credit calculation.

If the available balance is insufficient, the message is not accepted for delivery and Adal does not return 202 Accepted. Check the API response before treating the message as queued.

Refer to the dashboard for current pricing calculations, balance, and plan limits. Do not hard-code these values into your integration.

Errors and troubleshooting

Treat a message as accepted only after receiving 202 Accepted. Any other response means that the message has not been confirmed as queued.

Common causes of errors include:

  • an invalid, expired, or revoked token;

  • an expired access token;

  • an invalid request format;

  • an unsupported HTTP method;

  • a prohibited HTTP header;

  • an invalid destination or one blocked by SSRF protection;

  • insufficient balance;

  • a rate limit being exceeded.

Problem What to check
Cannot obtain an access token Make sure the refresh token exists and has not been revoked
Access token has expired Repeat /auth/outbound with a valid refresh token
Refresh token has been lost or revoked Create a new token under Outbound → Tokens
Cannot send a message through a region Use the current domain returned by the region list
API rejects a header Remove the reserved or hop-by-hop header
destination is rejected Make sure it is a public HTTP or HTTPS URL without embedded credentials
Message remains pending for a long time Open the attempt history in the correct region
Message has a failed status Review the technical error, HTTP statuses, and timings for each attempt
Message does not appear in the dashboard Select the same region through which the message was sent

Do not write tokens, the complete recipient URL, headers, or message body to ordinary logs or analytics systems: they may contain personal data or secrets.

Limitations

In the current Outbound workflow:

  • the application selects the region; there is no automatic selection or cross-region failover;

  • each message has one destination;

  • the recipient must be publicly accessible over HTTP or HTTPS;

  • the recipient's HTTP response body is not stored;

  • message and attempt history is available in the dashboard, but not through the public API.

Outbound does not provide exactly-once delivery, strict delivery ordering, or a guarantee that the recipient's business operation completed successfully.

Complete integration flow

1. Create a refresh token in the dashboard and use it to obtain an access token

POST https://cp.adal.cloud/auth/outbound Authorization: Bearer <refresh-token>

Save the refresh token when you create it in the dashboard. Use the returned access token for API requests.

2. Retrieve the region list

GET https://cp.adal.cloud/outbound/servers Authorization: Bearer <access-token>

Select a region and save the returned domain in your application's configuration.

3. Send a message

POST https://kz2.adal.cloud/api/send Authorization: Bearer <access-token> Content-Type: application/json
{ "destination": "https://example.com/webhooks", "method": "POST", "headers": { "Content-Type": ["application/json"], "X-Event-Id": ["evt_01K2..."] }, "body_base64": "eyJldmVudCI6Im9yZGVyLmNyZWF0ZWQifQ==", "max_attempts": 3, "add_idempotency": true }

Treat the message as accepted only after receiving 202 Accepted. Save its ID so you can match it to the history shown in the dashboard.

4. Renew the access token

When the access token expires, obtain a new one using the refresh token. Do not retrieve the region list again solely because the token was renewed.

5. Check the delivery result

Open Outbound → Messages, select the delivery region, and review the message status and attempt history.

API summary

Method Endpoint Authorization Purpose
POST https://cp.adal.cloud/auth/outbound Refresh token Obtain or renew an access token
GET https://cp.adal.cloud/outbound/servers Access token Retrieve the region list
POST https://{region-domain}/api/send Access token Queue a message for delivery
  • Destinations — deliver inbound Requests to configured Destinations

  • Retries — retry behavior for the inbound flow

  • Core concepts — Adal's core entities and architecture

  • Data storage — storage rules for inbound Requests

Related documentation