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:
Create a refresh token under Outbound → Tokens.
Exchange the refresh token for an access token through the Control Plane.
Retrieve the list of regions, select one, and save its domain.
Send messages directly to the selected region.
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
2xxresponse 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:
localhostand 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
destinationor 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 |
Related pages
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