Build integrations
Retries and alerts
Manual and bulk retry, retry chains, retry policies, budgets, alerts and notifiers
Manual retry#
The Exchanges page offers retry to members with exchanges.operate, for one exchange, a selection, or every exchange matching the current filters.
POST /api/xchanges/{id}/retry
Content-Type: application/json
{ "reason": "Partner fixed their endpoint", "reset": true }
- A retry is a new exchange with the original input, and
retryForset to the original's id. - Each exchange can be retried once. A second retry of the same exchange is refused with
ALREADY_RETRIED, so you continue from its newest attempt. The attempts form a chain, shown in the exchange drawer with each attempt named by its promoted properties, and returned byGET /api/xchanges/retrytree?id=. - With
reset: false, the retry reuses the mapper, handler and properties stored on the original exchange. This works even when the subscription has since been deleted. - With
reset: true, shown in the UI as Re-resolve adapter properties, the retry uses the subscription's current configuration and resolves partner and global tokens again. - Retrying an exchange that has an automatic retry waiting is refused. Use Run now on the waiting retry instead.
- Retry policies ignore manual retries. A manual retry never spends a budget and never starts an automatic chain, and its result says so.
Bulk retry#
POST /api/xchanges/bulkretry retries either a list of ids, or everything a search filter matches minus excludeIds. POST /api/xchanges/bulkretrypreview returns the same plan without running it, and the UI shows that plan before you confirm.
- A bulk retry covers at most 500 exchanges. A larger selection is refused, so narrow the filter.
- An exchange that was already retried is replaced by the newest attempt of its chain. Two selections that lead to the same attempt are retried once.
- The plan skips, with a reason, exchanges that no longer exist, that succeeded, that have an automatic retry waiting, whose subscription is gone when
resetis on, or whose input file cannot be read. - Exchanges with no result yet are included, so work stranded by an outage can be recovered. Make sure nothing is still processing before retrying it.
- The Latest attempt only filter,
LatestOnly:1:true, shows only the end of each chain, which is usually what needs retrying.
Retry policies#
A retry policy decides whether a failed exchange is retried automatically, when, and how many times.
A subscription uses either a shared policy, managed on the Retry policies page, or an inline policy stored on the subscription itself. Setting one clears the other.
Groups#
A policy is a list of groups. When an exchange fails, the first group that applies decides.
| Field | Meaning |
|---|---|
name, notes |
|
priority |
Lower numbers are checked first. |
enabled |
Disabled groups are skipped. |
appliesTo |
Error for an exception, BadResult for a response flagged bad, or both. |
matchers |
Conditions. Any one of them selects the group. |
action |
Allow retries within the budget. Block stops retries for the failures it matches. |
budget |
Required for Allow. Holds the per-message limit, the shared total and the delay strategy. |
alertMode, alertHandlerId, alertHandlerProperties |
Where the budget-exhausted alert goes. |
For Error, matchers look at the full exception text. For BadResult, they look at the response body.
Matchers#
type |
Works on | Fields | Matches when |
|---|---|---|---|
contains |
Error, BadResult | value, caseSensitive (default false) |
The text contains value |
regex |
Error, BadResult | pattern, flags (default "i"; "" is case-sensitive) |
The .NET regular expression matches within 200 ms |
exceptionType |
Error | value, includeInner (default true) |
An exception type named in the text equals value, short or fully qualified. With includeInner off, only the outermost exception counts. |
jsonPath |
BadResult | path, op, value |
The JSON body meets the condition. path uses dots and [index]. op is Eq, Neq, Contains, Regex, Exists or NotExists, ignoring case. Invalid JSON never matches. |
There is no status-code matcher. To match an HTTP status, use contains or regex on the exception text or body.
Delay strategies#
The index is 0 for the first retry.
type |
Fields | Delay |
|---|---|---|
fixed |
delayMs |
Always delayMs |
linear |
initialDelayMs, incrementMs |
initialDelayMs + index × incrementMs |
exponential |
initialDelayMs, multiplier (default 2), maxDelayMs (default 30000) |
initialDelayMs × multiplier^index, capped at maxDelayMs |
Budgets#
A budget sets two limits.
maxAttemptsPerErrorcaps retries of one message. The count is the length of the exchange's retry chain.maxAttemptsTotalis a total shared by every message of one subscription in one group. It is not a rolling window. It only resets when someone resets it or when the subscription next succeeds.
When a subscription succeeds, each of its budgets that was already exhausted before that exchange started is released. Partly spent budgets are left alone.
Example group#
{
"name": "Transient network errors",
"priority": 1,
"enabled": true,
"appliesTo": ["Error"],
"matchers": [
{ "type": "exceptionType", "value": "HttpRequestException" },
{ "type": "contains", "value": "503:" }
],
"action": "Allow",
"budget": {
"maxAttemptsPerError": 5,
"maxAttemptsTotal": 500,
"delayStrategy": { "type": "exponential", "initialDelayMs": 30000, "multiplier": 2, "maxDelayMs": 1800000 }
},
"alertMode": "Inherit"
}
How a failure is evaluated#
- Manual retries and exchanges without a subscription are skipped.
- A failure that already has a waiting retry is not evaluated again, so a redelivered bus message cannot spend the budget twice.
- Bitween picks the first enabled group, by priority, whose
appliesToincludes the result type and whose matchers match. A group with no matchers matches everything. - If no group matched, there is no retry.
- If the group blocks, there is no retry.
- If the message reached
maxAttemptsPerError, there is no retry. This is checked first, so it does not spend the shared total. - If the shared total is spent, there is no retry. When this failure is the one that used up the total, an alert is raised.
- Otherwise a retry is scheduled after the delay.
When no retry is scheduled, the exchange result records one of these reasons.
- No matching group (default block)
- Group '…' explicitly blocks this error
- Group '…' allows retries but has no budget, so none can be scheduled
- Per-message cap reached (n) in group '…'
- Group total cap reached (n) for group '…'
- This attempt was started by hand, so the retry policy left it alone and its budget is untouched.
- The scheduled retry was dropped: the subscription it belonged to no longer exists.
- The scheduled retry was dropped: the input file could not be read.
- The scheduled retry was dropped: this exchange had already been retried, as ….
Saving rules#
A policy is refused when a group:
- has no result type or no matchers;
- has matchers that cannot apply to its result types;
- allows retries without a budget;
- sends alerts without a handler;
- sends alerts through
NativeSmtpHandlerwith a password and TLS turned off.
Deleting a policy is refused while a subscription uses it. Removing a group deletes its usage counters and alert overrides.
Testing a policy#
POST /api/retrypolicies/test runs groups against sample content without touching any budget.
{
"groups": [ ],
"resultType": "Error",
"content": "System.Net.Http.HttpRequestException: Connection refused",
"attemptsToSimulate": 5
}
It returns each simulated attempt with the matched group, whether it would retry, the delay and the reason, and stops at the first refusal. The policy page's test panel runs the unsaved draft.
Scheduled retries#
An automatic retry waits as a delayed retry until its time comes.
- The retry job looks for due retries every minute by default. The Retry poll schedule setting changes this, and saving it reschedules the job at once.
- A due retry creates a new exchange from the original input with the subscription's current configuration, then deletes the waiting record. If the new exchange fails too, it is evaluated with the next attempt number.
- When the original exchange, its subscription or its input file is gone, or the exchange has already been retried, the waiting record is dropped and the reason is written on the original result.
- A retry that throws is logged and removed, so one bad record cannot block the rest.
The Scheduled retries page lists waiting retries. Run now, POST /api/delayedretries/{exchangeId}/runnow, runs one straight away and needs exchanges.operate.
Budget-exhausted alerts#
When a group's shared total runs out, Bitween sends one alert through a handler adapter. The most specific level that decides wins.
| Level | Where it is set |
|---|---|
| One subscription in one group | Route alert in the policy page's usage panel |
| Group | The group's alert settings |
| Policy | The policy-wide alert card. Inline policies have no policy level. |
At each level, Silent stops the alert, Send with a handler sends it there, and Inherit defers to the level above. Send without a handler also defers. When no level decides, nobody is alerted.
The handler receives a JSON document with the exchange id, subscription id and name, information type name, correlation id, policy name, group name, total budget, blocked reason, exception and time.
Each delivery is recorded as a notification named Retry budget alert. Once one delivery succeeds, a redelivered event does not send a second alert.
Usage#
The policy page's usage panel shows, per subscription and group, the attempts used against the total, whether it is exhausted, the last failure, where the alert goes and whether it was delivered. From there you can reset a counter, route one subscription's alert elsewhere, or list the failures the group handled. A subscription's overview shows a banner once a budget has been spent, with its own reset button.
| Action | Endpoint | Permission |
|---|---|---|
| Usage for a policy | POST /api/retrypolicies/{id}/usage |
retry-policies.view |
| Failures a group handled | POST /api/retrypolicies/{id}/attempts |
retry-policies.view |
| Reset usage for a policy | POST /api/retrypolicies/{id}/resetusage |
retry-policies.edit |
| Route one subscription's alert | POST /api/retrypolicies/{id}/savealertoverride |
retry-policies.edit |
| Usage for one subscription | POST /api/subscriptions/{id}/retryusage |
subscriptions.view |
| Reset usage for one subscription | POST /api/subscriptions/{id}/resetretryusage |
subscriptions.operate |
Notifiers#
A notifier runs a handler adapter after exchanges of chosen subscriptions finish.
| Field | Meaning |
|---|---|
| Name | |
| Handler and properties | Any handler adapter, such as NativeSmtpHandler or NativeHttpHandler |
| Run on successful result | Fires for successes with a good response |
| Run on bad result | Fires for successes whose response was flagged bad |
| Run on failed result | Fires for failures |
| Watches | The subscriptions it listens to. A notifier that watches nothing never fires. |
| Inactive | Switches it off |
The handler receives a JSON document describing the result, with PascalCase fields: Id, Success, Exception, StartedOn, FinishedOn, OutputBad, ResponseBad, SubscriptionId, SubscriptionName, DocumentId, DocumentName and CorrelationId. Its properties also get xchangeid. Partner and global tokens are not resolved in notifier properties.
Each run is recorded with its outcome and any error. The notifier page lists recent notifications, and GET /api/notifications searches them. Notifiers run on each work group's -Result queue, separate from exchange processing.