Waiting for Approval
The requirement sounded modest: let Cisco XDR prepare a response, but require a person to approve it before anything potentially disruptive happened. The customer wanted analysts to keep the speed of automation for collection and enrichment while retaining control over the final action. A detection could arrive, gather context, identify the affected asset, and propose a response without waiting for an operator. The response itself had to wait.
That distinction mattered. This was not a notification project, and it was not a workflow that would act first and create a ticket afterward. Approval had to be a real execution boundary. An approver needed enough evidence to make a decision, rejection needed to end the action cleanly, and silence needed to expire rather than becoming implied consent.
The intended journey crossed system boundaries. XDR would own the detection, enrichment, proposed action, and response state. An external approval system would present the request and record the human decision. A callback would carry that decision back to XDR so the correct paused execution could continue. On a diagram, it looked like a short line out and another line back.
The difficult work was hidden in that returning line.
The first test exposed the real problem
We began with a thin end-to-end path because it gave both teams something concrete to challenge. A sample incident entered XDR, the workflow prepared a proposed response, the external system recorded approval, and a callback arrived. The waiting run resumed and reached a harmless test connector. At first glance, the integration worked.
Then we sent the same callback again.
The second request was still authentic. It came from the expected sender, carried a valid decision, and referred to a real request. In the first version, that was enough to let it enter the continuation path again. Our test connector was deliberately safe, but the behavior proved that authentication alone did not make the callback safe. A legitimate message could be delayed, retried by the sender, or replayed after interception. One human decision must never become permission for repeated response attempts.
That result changed the project conversation. The central question was no longer, “Can the approval system call XDR?” It was, “Can XDR prove that this particular approval belongs to this particular waiting action, is still current, and has not already been consumed?”
We reviewed the ownership boundary with the customer’s security operations and service-management participants. XDR would remain authoritative for whether a response was pending, eligible, expired, or completed. The external system would remain authoritative for who decided and what that decision was. Neither side would infer the other side’s state from a ticket number, a work note, or the mere arrival of an HTTP request.
We made the pause visible in the workflow
The XDR workflow used the real Request Approval task, not a delay block dressed up with a notification. The task pauses the workflow and represents an explicit human decision point. Before it, the workflow assembled the evidence and proposed action. After it, separate branches handled approval, rejection, and the absence of a timely decision. The response block was reachable only from the validated approved branch.

That layout helped the joint review. The team could inspect the workflow from left to right and see where unattended automation ended. The blocks before the pause received the incident, normalized identifiers, enriched the asset and identity, evaluated policy, and built the approval summary. The Request Approval task held execution. The blocks after the decision checked state and selected a branch. Only then could the response connector run. Final blocks recorded whether the provider accepted the request and whether verification showed that the action succeeded.
We were careful not to make “approved” mean “completed.” Approval meant the response connector was allowed to be called. Accepted meant the downstream provider had received the request. Succeeded meant the expected result had been verified. A connector error or failed verification produced a response failure requiring reconciliation; it did not reopen the approval or silently retry the disruptive action.
The information presented for approval was also part of the boundary. The request contained the finding summary, the relevant evidence, the classified target, the proposed action, and the reason a human decision was required. It did not ask someone to approve a vague label. If enrichment changed the target or materially changed the proposal while the workflow waited, the old decision could not authorize the new context.
The Webhook Rule was the controlled way back
The return path used XDR’s Webhook Rule automation type. Creating that rule produces an XDR-generated endpoint for an external system to call. The endpoint is not an XDR client credential and does not give the external system general access to start arbitrary response actions. It is a trigger into a defined automation path, where the request can be authenticated, correlated, and checked against durable waiting state.

That distinction removed an early ambiguity. The Webhook Rule did not mean, “Any POST to this URL runs containment.” It meant, “A request arriving here may ask to resolve a known pending approval.” The rule accepted the callback and passed its narrow payload into validation. The workflow decided whether any run was eligible to resume.
For the project, the external approval system sent only the fields needed for that decision:
{
"correlation_id": "<paused-run-correlation>",
"decision": "approved",
"approval_reference": "<approval-reference>",
"ticket": "<ticket-number>"
}
The correlation value was generated for the pending action and stored on both sides. It was not the human-readable ticket number, because one incident could produce more than one proposed action over its lifetime and ticket numbers could not describe workflow execution. The approval reference identified the decision record. The decision value came from a small allowed set. The ticket value was retained for traceability, not used as the sole resume key.
The callback was emitted by the approval-state transition. Editing a description, reassigning the record, or adding a work note did not send it. This kept ordinary case activity from becoming an automation command. For a manual approval, the person acted in the external system; that state change caused the system integration to call the XDR-generated endpoint. The callback then resumed the matching paused workflow only after every control passed.
Authentication was necessary, then state did the rest
We treated the endpoint as exposed to untrusted requests even though its caller was known. The sender authenticated to the callback path using the integration’s supported protected credential mechanism, and transport encryption protected the request in transit. Secrets remained in the external system’s credential or protected-property storage. They were not copied into ticket text, work notes, workflow output, or screenshots.
After authenticating the sender, the receiving workflow validated the message shape and decision value. It looked up the pending record by correlation value, compared the approval reference, checked the expected ticket relationship, and confirmed that the request had not expired. It also compared the stored proposal context with the context eligible for execution. A valid signature on an unknown, stale, or mismatched correlation value still resulted in no action.
The pending record carried more than a Boolean flag. It represented a state transition from waiting to consumed. The first valid decision acquired that transition and recorded its result. Any duplicate carrying the same correlation and approval reference received the already-recorded outcome without re-entering the response branch. A conflicting second decision was rejected and surfaced for investigation. This made sender retries harmless and closed the replay gap found in the first test.
Expiry was explicit. A late approval did not revive a response after its decision window closed. Rejection was explicit too: it recorded a normal business outcome and stopped, rather than appearing as a failed automation run. If a callback arrived while the associated incident or asset context no longer matched the proposal, the workflow held the action and required a fresh request instead of stretching the meaning of the earlier approval.
Collaboration turned edge cases into acceptance tests
The customer’s operators contributed the most useful questions during review. What would the next shift see if approval arrived but response execution failed? What if the callback timed out and the external system retried? What if two requests were waiting for the same incident? What if an approver rejected one proposal and approved a later one? Each question became a state transition or an acceptance test rather than a note left for production.
We tested approved, rejected, and expired paths separately. Approval resumed only the matching waiting run. Rejection recorded the decision and never called the response connector. Expiry closed the pending state, and a callback delivered afterward could not reopen it. An unsupported decision value and a malformed body stopped at validation.
We then tested identity and correlation failures. A request without valid authentication did not reach state lookup. A validly authenticated request with an unknown correlation value produced no response action. A callback with the right ticket but the wrong correlation value did not match. Two concurrent pending runs were approved in reverse order, and each callback resumed only its own workflow.
The replay test that had failed initially became the key regression test. We delivered an approval once, confirmed a single call to the test response connector, and delivered the identical callback again. The second request returned the stored decision outcome. Connector invocation remained at one. We also sent concurrent duplicate deliveries to make sure the control was atomic rather than dependent on requests arriving neatly one after another.
Finally, we changed the proposal while approval was pending. The workflow detected that the approved context no longer matched the response context and did not proceed. We simulated a response connector failure after a valid approval and confirmed that reconciliation recorded the failure without consuming the approval a second time. A reporting failure after successful execution likewise created follow-up work instead of retrying the action.
The delivered outcome was a bounded decision, not just a webhook
At handoff, the customer had an approval-gated XDR workflow whose control point could be explained from both the workflow canvas and the operational record. Analysts could see the evidence and proposed action before deciding. They could distinguish waiting, rejected, expired, approved, execution accepted, execution succeeded, and execution failed. They did not need to inspect raw workflow output to learn what happened.
The Request Approval task provided the genuine pause. The Webhook Rule provided an XDR-generated endpoint for the external return path. Authentication established who sent the callback; correlation established which pending action it concerned; current-state checks established whether that action was still eligible; and one-time consumption made retries and replay safe. The downstream response remained unreachable until all four were true.
The sanitized screenshots retained here show the two XDR mechanics that mattered in the project without exposing the environment: the Request Approval block in the workflow and the Webhook Rule automation type. Account names, callback URLs, run identifiers, targets, ticket numbers, and approval references remain out of view.
What we delivered was not a generic webhook pattern and not a ticket-shaped notification. It was a controlled handoff between automated preparation and human authority. The first replay test showed why the difference mattered. The finished workflow could wait, accept one accountable decision, resume exactly the intended run, and prove that the same permission could not be spent twice.