API Command Processing, Retries and Webhook Events
When you submit a LinkedIn activity through the API, or when a Campaign generates one, the command is placed in the activity queue before execution. This article explains what happens next, which failures STACKED or legacy Dux-Soup extension retries automatically, how retries are represented in Webhook events, and when an integration can safely submit another command.
Some failures are temporary. When one of these occurs, the robot can automatically schedule another attempt rather than permanently failing the activity.
This article explains:
- how queued commands are processed
- which errors are retried automatically
- the automatic retry schedule
- how retries appear in Webhook events
- how to correlate multiple attempts of the same activity
- which events indicate that processing has finished
- when it is safe to resend a command
Understanding rccommand events
Checking what is currently queued
When is it safe to resend a command?
Command lifecycle
A command submitted through the API is first added to the activity queue.
A typical successful command follows this lifecycle:
queued → received → completed
A command that encounters a temporary error may follow:
queued → received → error → retry → queued → received → completed
A command can also finish with an ignore event, or it can be removed from the queue before execution:
queued → removed
An important distinction is that a command leaves the queue when STACKED accepts it for execution, not when the LinkedIn action completes.
This is when the rccommand received event is generated.
The subsequent completed, error, or ignore event describes what happened to a command that has already left the queue.
Automatic retries
Dux-Soup automatically retries commands only when the error indicates a temporary condition.
This applies to queued commands submitted directly through the API as well as commands generated by Campaigns.
The following errors are eligible for automatic retry:
| Error | Description |
|---|---|
SessionError |
No active LinkedIn session |
CaptchaError |
A CAPTCHA prevented the action from completing |
ActionTimeoutError |
The action did not complete within 5 minutes |
PageElementSelectorError |
A required page element could not be found |
InviteQuotaReachedError |
The LinkedIn invitation quota was reached |
InMailLimitReachedError |
The LinkedIn InMail limit was reached |
OutOfInMailCreditsError |
No InMail credits were available |
Use data.error.name in the rccommand error Webhook event to identify the error.
Retry schedule
A command can be attempted up to 5 times in total, including the initial attempt.
| Attempt | Scheduled |
|---|---|
| 1 | Initial execution |
| 2 | 1 hour after the failure |
| 3 | 1 day after the failure |
| 4 | 3 days after the failure |
| 5 | 14 days after the failure |
The scheduled retry time is the earliest time the command can run. The user’s automation schedule, throttling, daily limits, and other applicable rules still apply.
If the Campaign associated with the command has been switched off, the command is not retried.
Errors that are not retried
All other error types are treated as final and do not result in an automatic retry.
These include:
InviteErrorInviteEmailRequiredErrorInvitePendingRequestErrorInviteSendErrorProfileNotFoundErrorInvalidActionTargetErrorDirectMessageErrorMessageBoxNotFoundErrorMultipleMessageBoxesFoundErrorInMailNotAllowedErrorInMailValidationErrorEnrollErrorRepeatedEnrollErrorUnknownCampaignIDErrorHTTPRequestFailedErrorAutomationErrorDuxError
An ignore outcome is also never retried.
When a command fails with one of these errors, it is no longer in the queue and will not run again automatically.
How retries work
A retry is not the same command being placed back into the queue.
Each retry is created as a new command with its own id .
The new command contains the original params together with two additional fields:
attempt — the attempt number, from 2 to 5
retryof — the ID of the original command
For example:
{
"type": "rccommand",
"event": "retry",
"data": {
"id": "b7e2c1d4-4f0a-9c3e5a7d1b2f",
"command": "message",
"params": {
"profile": "https://www.linkedin.com/in/example-profile/",
"messagetext": "Hello",
"campaignid": "default",
"attempt": 2,
"retryof": "a5f64143-b847-5f473ac7488d"
},
"when": "2026-10-14T16:03:21.000Z"
}
}
data.id is the ID of the new command.
data.when is the earliest time at which that command can be executed.
The retryof value remains the same for every subsequent attempt. It always identifies the original command in the retry chain.
Correlating retries
When tracking an activity across multiple attempts, use:
params.retryof when it is present.
Otherwise, use the event’s own command id.
In other words:
Retry-chain correlation key = params.retryof if present; otherwise the command’s own id .
This allows your integration to treat the original command and all of its retries as one logical activity, even though each attempt has its own command ID.
Understanding rccommand events
Webhook events can be used to follow a command from scheduling through execution.
The main command states are:
| Event/state | Terminal? | Meaning |
|---|---|---|
| No event yet | No | Command may still be queued |
snooze |
No | STACKED declined work for now |
received |
No | Command has been accepted for execution and has left the queue |
retry |
No | A new attempt has been scheduled |
completed |
Yes | The LinkedIn action completed |
ignore |
Yes | The action/profile was skipped |
removed |
Yes | Command was removed before delivery |
error |
Depends | Final unless an automatic retry is scheduled |
completed
A completed event means the action ran successfully.
No further attempt in that retry chain is required.
ignore
An ignore event means the action was skipped and will not be retried.
Possible data.reason values include:
blacklistednot enough connectionstagged as skippedNo name found for profileNo link found for profileNo thumbnail
An ignore event is terminal.
removed
A removed event means that the command was taken out of the queue before it was delivered for execution.
Possible data.reason values include:
deletedresetcampaign-clearedcampaign-deletedunenrolledsuperseded
For example:
{
"userid": "100000000000000000000",
"time": "2026-09-30T11:31:29.838Z",
"type": "rccommand",
"event": "removed",
"data": {
"id": "188fc5c4-892f-149d751cdb2c",
"command": "message",
"params": {
"profile": "https://www.linkedin.com/in/example-profile/",
"profileid": "id.123456789",
"campaignid": "my-campaign",
"actionindex": 5,
"attempt": 1,
"messagetext": "Hi FN, thanks for connecting..."
},
"reason": "unenrolled",
"by": "100000000000000000000"
}
}
When the reason is superseded, a newer connection command for the same profile has replaced the existing queued command. In this case, data.replacedby contains the ID of the replacement command.
A removed event is terminal for the removed command.
error and retry
An error event does not necessarily mean that the activity is permanently finished.
If the error is eligible for automatic retry, Dux-Soup schedules another attempt and sends a separate rccommand retry event.
The error and retry events are generated by different senders. As a result, they can reach your Webhook endpoint in either order.
Do not therefore treat an eligible error event as immediately terminal.
Allow a few minutes for the corresponding retry event to arrive before concluding that no retry has been scheduled.
An error from attempt 5 is always final, because the maximum number of attempts has been reached.
Checking what is currently queued
You can query the activity queue at any time using the Queue Management API:
GET .../queue/items
The request can optionally be filtered by campaignid or command.
Each queued item includes its command id and params, allowing your integration to determine whether a particular command or retry is still waiting for execution.
This can be useful when reconciling your own integration state with Webhook events.
When is it safe to resend a command?
It is generally safe to submit a new command once the existing retry chain has reached a terminal state.
This includes:
- after
completed, if you intentionally want to perform the activity again - after
ignore - after
removed - after an
errorthat was not followed by aretry
At that point, the previous command will not be processed again automatically.
Whether resending is useful depends on the reason the previous command finished.
For example, immediately resending after ProfileNotFoundError or InvitePendingRequestError is likely to produce the same result unless the underlying condition has changed.
When not to resend
Do not manually resend while a command in the retry chain:
- is still queued
- has been
receivedand is currently executing - has a pending automatic retry
Doing so could result in more than one command being executed.
If you prefer to handle a retry yourself, first delete the pending retry from the Queue page in the dashboard. A removed event will be generated for that command.
Identical commands
If you submit the exact same command with the exact same parameters while the original is still queued, the API does not create a duplicate. It returns the existing command ID.
However, any difference in params creates a different command.
This includes additional custom fields.
If the parameters differ, both commands can therefore be queued and executed.
Received but no outcome event
There is one important edge case to consider.
The 5-minute action timeout operates inside STACKED or the applicable legacy Dux-Soup extension.
If the browser closes, the computer sleeps, or the active session is interrupted after a command has been received, the command may produce no subsequent completed, error, ignore, or retry event.
The command has already left the queue and will not automatically resume.
If a command has been received but no outcome event has arrived after approximately 15 minutes, you can treat it as abandoned.
However, use caution before resending.
The LinkedIn action may have completed immediately before the interruption, even though the outcome event was never generated.
For a message command, for example, check the recent conversation before resending:
GET .../conversations/{profileid}/recent?direction=sent
You can also use your Message Webhook events to determine whether the message was sent.
Check GET .../queue/items for the command ID as well. In the rare case where the extension’s acknowledgement was lost in transit, the command may still be present in the queue and could run again automatically.
Using your own correlation reference
You can add your own fields to a command’s params.
For example:
{
"profile": "https://www.linkedin.com/in/example-profile/",
"messagetext": "Hello",
"crmReference": "lead-84721"
}
Additional fields are stored with the command and returned in subsequent events.
They are also preserved when Dux-Soup creates an automatic retry.
This can be useful when integrating the API with a CRM, lead management platform, or another external system because you can include your own record identifier and use it to correlate Webhook events with the original record.
Webhook delivery
Webhook delivery is best effort.
STACKED or legacy Dux-Soup attempts to deliver each Webhook event up to 3 times.
Webhook events are also retained in your Dux-Soup account history, which can be useful when investigating an event that was not successfully received by your endpoint.
Your integration should be designed to tolerate duplicate Webhook deliveries and events arriving in a different order than expected.
In particular, remember that error and retry events can arrive in either order.
Summary
When processing API and Campaign commands, keep these rules in mind:
- a command leaves the queue when you receive the
receivedevent - temporary errors can be retried automatically
- there are a maximum of 5 attempts
- each retry is a new command with its own
id - use
params.retryofto correlate a retry chain completed,ignore, andremovedare terminal outcomes- an
errormay be followed by aretry, so allow time for both events to arrive - attempt 5 errors are always final
- don’t manually resend while an automatic retry is pending
- use
GET .../queue/itemsto check what is still queued - treat
receivedwith no outcome after approximately 15 minutes as abandoned, but verify that the LinkedIn action did not complete before resending - custom fields in
paramscan be used to correlate commands with records in your own system