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


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:

  • InviteError
  • InviteEmailRequiredError
  • InvitePendingRequestError
  • InviteSendError
  • ProfileNotFoundError
  • InvalidActionTargetError
  • DirectMessageError
  • MessageBoxNotFoundError
  • MultipleMessageBoxesFoundError
  • InMailNotAllowedError
  • InMailValidationError
  • EnrollError
  • RepeatedEnrollError
  • UnknownCampaignIDError
  • HTTPRequestFailedError
  • AutomationError
  • DuxError

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:

  • blacklisted
  • not enough connections
  • tagged as skipped
  • No name found for profile
  • No link found for profile
  • No 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:

  • deleted
  • reset
  • campaign-cleared
  • campaign-deleted
  • unenrolled
  • superseded

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 error  that was not followed by a retry

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 received  and 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 received  event
  • 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.retryof  to correlate a retry chain
  • completed , ignore , and removed  are terminal outcomes
  • an error  may be followed by a retry, 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/items  to check what is still queued
  • treat received  with no outcome after approximately 15 minutes as abandoned, but verify that the LinkedIn action did not complete before resending
  • custom fields in params  can be used to correlate commands with records in your own system