Blu Pantheon
Automazioni & RPAPiattaforme RPA

Automation Anywhere

Collegare Automation Anywhere A360 a Blu Pantheon con il pacchetto REST Web Service, Credential Vault e bot API-triggered.

Guida per Automation Anywhere A360 (Control Room cloud o on-premise): chiamare le API di Blu Pantheon dal bot e ricevere eventi tramite un listener intermedio che avvia un bot API-triggered.

Prima di iniziare

  • Automation Anywhere A360 con licenza Bot Creator e Bot Runner
  • Permessi per creare Locker e Credential nel Credential Vault
  • La chiave API bp_live_xxxxxxxxxxxxxxxx e il segreto whsec_xxxxxxxxxxxxxxxx
  • Un listener HTTPS pubblico che verifica la firma e chiama le API del Control Room

Architettura

Automation Anywhere non espone endpoint HTTP pubblici per il consumo diretto dei webhook. Il pattern corretto è:

Blu Pantheon ──webhook firmato──► Listener pubblico
                                       │ verifica HMAC, risponde 202
                                       ▼
                        Control Room API /v3/automations/deploy
                                       │
                                       ▼
                                  Bot Runner
                                       ├──► Gestionale (UI automation)
                                       └──► API Blu Pantheon (REST Web Service)

Parte 1 — Credential Vault

Crea il Locker

Vai su Manage → Credentials → Lockers → Create locker.

CampoValore
Locker nameBluPantheon
DescriptionCredenziali integrazione Blu Pantheon
Ownersil team di automazione
Consumersi ruoli che eseguono i bot

Crea la credenziale

Vai su Credentials → Create credential.

CampoValore
Credential nameBluPantheon_Api
Attribute 1 → Nameapi_key
Attribute 1 → Valuebp_live_xxxxxxxxxxxxxxxx
Attribute 1 → Input typePassword
Attribute 2 → Nameproject_id
Attribute 2 → Valuebp_proj_7f3a9c21
Attribute 2 → Input typeText
Attribute 3 → Namebase_url
Attribute 3 → Valuehttps://api.blupantheon.com
Attribute 3 → Input typeText

Associa la credenziale al Locker BluPantheon.

Leggi la credenziale nel bot

Nel Bot Editor crea le variabili:

Nome variabileTipoOrigine
vApiKeyStringCredential → BluPantheon / BluPantheon_Api / api_key
vProjectIdStringCredential → BluPantheon / BluPantheon_Api / project_id
vBaseUrlStringCredential → BluPantheon / BluPantheon_Api / base_url

Imposta vApiKey come Private (spunta Private nelle proprietà della variabile) per impedirne la registrazione nei log di esecuzione.

Variabili private obbligatorie

Senza il flag Private, il valore della chiave compare nei log di debug e negli screenshot di errore che il Control Room salva automaticamente al fallimento del bot.

Parte 2 — Chiamare le API con REST Web Service

Azione GET

Usa REST Web Service → Get method.

CampoValore
URI$vBaseUrl$/identity/v1/sessions/$vSessionId$
Authentication modeCustom
Custom headersvedi tabella
Content typeapplication/json
Output → Assign the output to a variablevResponse (Dictionary)

Custom headers:

KeyValue
AuthorizationBearer $vApiKey$
X-BP-Project$vProjectId$
Acceptapplication/json

Azione POST

Usa REST Web Service → Post method.

CampoValore
URI$vBaseUrl$/identity/v1/sessions/$vSessionId$/notes
Authentication modeCustom
Custom headerscome sopra, più Idempotency-Key: $vDeliveryId$ e Content-Type: application/json
Body → Raw$vRequestBody$
OutputvResponse (Dictionary)

Costruisci il corpo con l'azione String → Replace su un modello, per evitare problemi con gli apici:

Modello del corpo, variabile vBodyTemplate
{"body":"__NOTA__","visibility":"internal","author":"automation-anywhere"}
AzioneConfigurazione
String → ReplaceSource $vBodyTemplate$, Find __NOTA__, Replace with $vNota$, Output vRequestBody

Attenzione ai caratteri speciali

Se il testo della nota può contenere virgolette, apici o ritorni a capo, effettua l'escape prima della sostituzione: sostituisci " con \" e i ritorni a capo con \n usando due ulteriori azioni String → Replace.

Parte 3 — Leggere la risposta JSON

L'azione REST restituisce un Dictionary con le chiavi statusCode, headers e body.

Estrai il corpo

Azione String → Assign:

CampoValore
Source$vResponse{body}$
OutputvResponseBody (String)

Converti in oggetto JSON

Usa il pacchetto JSON, azione Start:

CampoValore
JSON string$vResponseBody$
Session namebpSession

Leggi i singoli campi

Azione JSON → Get single node value:

CampoValore
Session namebpSession
Node path$.data.id
OutputvNoteId (String)

Altri percorsi utili:

PercorsoContenuto
$.data.sessionIdIdentificativo della sessione
$.data.client.displayNameNome del cliente
$.data.referenceRiferimento della prenotazione
$.data.classification.categoryCategoria del documento
$.pagination.nextCursorCursore per la pagina successiva

Itera sugli array

Azione JSON → Get multiple node values con Node path $.data[*].id e output in una List. Poi usa Loop → For each item in list.

Chiudi la sessione JSON

Azione JSON → End con Session name bpSession. Rilascia la memoria e previene conflitti tra iterazioni.

Parte 4 — Gestione degli errori

Avvolgi in Try/Catch

Inserisci le azioni REST dentro Error handling → Try. Aggiungi un blocco Catch con Assign the error message to vErrorMessage e Assign the line number to vErrorLine.

Verifica il codice di stato

Dentro il Try, dopo la chiamata REST, aggiungi If → Number condition:

CampoValore
Source$vResponse{statusCode}$
ConditionGreater than or equal to
Value400

Nel ramo positivo lancia un'eccezione con Error handling → Throw, messaggio Errore API Blu Pantheon: $vResponse{statusCode}$.

Implementa il backoff sul 429

AzioneConfigurazione
If$vResponse{statusCode}$ uguale a 429
String → AssignvRetryAfter = $vResponse{headers}{Retry-After}$
Delay$vRetryAfter$ secondi
Loopripeti la chiamata, massimo 3 iterazioni

Registra l'errore nel Control Room

Nel blocco Catch usa Log to file verso $vLogPath$ e Message box solo in modalità attended. In modalità unattended invia una notifica email con l'azione Email → Send.

Chiudi sempre le risorse

Nel blocco Finally inserisci JSON → End e la chiusura delle applicazioni aperte, per evitare che un fallimento lasci sessioni del gestionale bloccate.

CodiceAzione consigliata
401Throw immediato, nessun ritentativo. Notifica il team: la chiave è scaduta o revocata.
403Throw immediato. Scope insufficienti sulla chiave.
404Verifica il valore di vSessionId. Non ritentare.
409Leggi la risorsa esistente e prosegui: l'operazione era già stata eseguita.
429Attendi Retry-After e ritenta fino a 3 volte.
5xxRitenta con backoff, poi Throw.

Parte 5 — Ricevere i webhook

Listener e avvio del bot

Il listener verifica la firma e avvia il bot tramite l'API del Control Room.

listener.py — verifica firma e deploy del bot
import hashlib
import hmac
import json
import os
import time

import requests
from fastapi import FastAPI, Request, Response

app = FastAPI()

TOLERANCE_SECONDS = 300
SECRET = os.environ["BP_WEBHOOK_SECRET"]
CR_URL = os.environ["AA_CONTROL_ROOM_URL"]
CR_TOKEN = os.environ["AA_API_TOKEN"]
BOT_FILE_ID = os.environ["AA_BOT_FILE_ID"]
RUNNER_USER_ID = os.environ["AA_RUNNER_USER_ID"]

seen_deliveries: set[str] = set()


def verify(raw_body: bytes, header: str | None, now: int) -> tuple[bool, str]:
    if not header:
        return False, "header-mancante"

    timestamp = None
    signatures = []
    for segment in header.split(","):
        key, separator, value = segment.partition("=")
        if not separator:
            continue
        if key.strip() == "t":
            try:
                timestamp = int(value.strip())
            except ValueError:
                return False, "timestamp-non-valido"
        elif key.strip() == "v1":
            signatures.append(value.strip())

    if timestamp is None:
        return False, "timestamp-non-valido"
    if abs(now - timestamp) > TOLERANCE_SECONDS:
        return False, "timestamp-fuori-tolleranza"
    if not signatures:
        return False, "firma-assente"

    signed = f"{timestamp}.".encode("utf-8") + raw_body
    expected = hmac.new(SECRET.encode("utf-8"), signed, hashlib.sha256).hexdigest()

    for candidate in signatures:
        if hmac.compare_digest(candidate, expected):
            return True, "ok"

    return False, "firma-non-corrispondente"


def deploy_bot(event: dict, delivery_id: str) -> None:
    payload = {
        "fileId": BOT_FILE_ID,
        "runAsUserIds": [int(RUNNER_USER_ID)],
        "poolIds": [],
        "overrideDefaultDevice": False,
        "botInput": {
            "vEventType": {"type": "STRING", "string": event["event"]},
            "vDeliveryId": {"type": "STRING", "string": delivery_id},
            "vEventId": {"type": "STRING", "string": event["id"]},
            "vPayload": {"type": "STRING", "string": json.dumps(event, ensure_ascii=False)},
        },
    }

    response = requests.post(
        f"{CR_URL}/v3/automations/deploy",
        headers={"X-Authorization": CR_TOKEN, "Content-Type": "application/json"},
        json=payload,
        timeout=10,
    )
    response.raise_for_status()


@app.post("/webhooks/blupantheon")
async def receive(request: Request) -> Response:
    raw_body = await request.body()
    valid, reason = verify(
        raw_body,
        request.headers.get("X-BP-Signature"),
        int(time.time()),
    )

    if not valid:
        status = 400 if reason == "timestamp-fuori-tolleranza" else 401
        return Response(
            content=json.dumps({"error": reason}),
            status_code=status,
            media_type="application/json",
        )

    delivery_id = request.headers.get("X-BP-Delivery", "")
    if delivery_id in seen_deliveries:
        return Response(status_code=202)
    seen_deliveries.add(delivery_id)

    event = json.loads(raw_body)
    deploy_bot(event, delivery_id)

    return Response(status_code=202)

Non attendere il bot

L'endpoint /v3/automations/deploy restituisce un deploymentId senza attendere il termine dell'esecuzione. Non aggiungere polling nel listener: risponderesti oltre i 10 secondi.

Coda intermedia per carichi elevati

Se gli eventi superano il numero di Bot Runner disponibili, inserisci una coda tra listener e Control Room:

ComponenteRuolo
ListenerVerifica firma, accoda, risponde 202
Coda (RabbitMQ, Service Bus, tabella SQL)Assorbe i picchi
DispatcherLegge dalla coda e chiama deploy rispettando la concorrenza massima
Work Queue di A360Alternativa nativa, con priorità e ritentativi integrati

Leggere gli input nel bot

Nel Bot Editor definisci le variabili di input con Use as → Input:

NomeTipo
vEventTypeString
vDeliveryIdString
vEventIdString
vPayloadString

Poi apri vPayload con il pacchetto JSON come descritto nella Parte 3.

Procedura di test

Test della chiamata API

Crea un bot minimo con una sola azione REST Web Service → Get su $vBaseUrl$/identity/v1/me. Il risultato atteso è statusCode uguale a 200.

Test del listener

Dalla Console invia un evento di prova webhook.test. Verifica che i log consegne mostrino 202 e che nel Control Room compaia un nuovo deployment in Activity → In progress.

Test della deduplica

Dai log consegne clicca Riprova. Non deve essere avviato un secondo deployment.

Test del ciclo completo

Genera un handover reale. Verifica che il bot apra la pratica nel gestionale e che la nota con il numero di pratica compaia nella sessione in Console.

Test della gestione errori

Revoca temporaneamente la chiave in un ambiente di prova e verifica che il bot fallisca con messaggio esplicito nel Catch, senza ritentativi inutili.

Passi successivi

In questa pagina