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.
| Campo | Valore |
|---|---|
| Locker name | BluPantheon |
| Description | Credenziali integrazione Blu Pantheon |
| Owners | il team di automazione |
| Consumers | i ruoli che eseguono i bot |
Crea la credenziale
Vai su Credentials → Create credential.
| Campo | Valore |
|---|---|
| Credential name | BluPantheon_Api |
| Attribute 1 → Name | api_key |
| Attribute 1 → Value | bp_live_xxxxxxxxxxxxxxxx |
| Attribute 1 → Input type | Password |
| Attribute 2 → Name | project_id |
| Attribute 2 → Value | bp_proj_7f3a9c21 |
| Attribute 2 → Input type | Text |
| Attribute 3 → Name | base_url |
| Attribute 3 → Value | https://api.blupantheon.com |
| Attribute 3 → Input type | Text |
Associa la credenziale al Locker BluPantheon.
Leggi la credenziale nel bot
Nel Bot Editor crea le variabili:
| Nome variabile | Tipo | Origine |
|---|---|---|
vApiKey | String | Credential → BluPantheon / BluPantheon_Api / api_key |
vProjectId | String | Credential → BluPantheon / BluPantheon_Api / project_id |
vBaseUrl | String | Credential → 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.
| Campo | Valore |
|---|---|
| URI | $vBaseUrl$/identity/v1/sessions/$vSessionId$ |
| Authentication mode | Custom |
| Custom headers | vedi tabella |
| Content type | application/json |
| Output → Assign the output to a variable | vResponse (Dictionary) |
Custom headers:
| Key | Value |
|---|---|
Authorization | Bearer $vApiKey$ |
X-BP-Project | $vProjectId$ |
Accept | application/json |
Azione POST
Usa REST Web Service → Post method.
| Campo | Valore |
|---|---|
| URI | $vBaseUrl$/identity/v1/sessions/$vSessionId$/notes |
| Authentication mode | Custom |
| Custom headers | come sopra, più Idempotency-Key: $vDeliveryId$ e Content-Type: application/json |
| Body → Raw | $vRequestBody$ |
| Output | vResponse (Dictionary) |
Costruisci il corpo con l'azione String → Replace su un modello, per evitare problemi con gli apici:
{"body":"__NOTA__","visibility":"internal","author":"automation-anywhere"}| Azione | Configurazione |
|---|---|
| String → Replace | Source $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:
| Campo | Valore |
|---|---|
| Source | $vResponse{body}$ |
| Output | vResponseBody (String) |
Converti in oggetto JSON
Usa il pacchetto JSON, azione Start:
| Campo | Valore |
|---|---|
| JSON string | $vResponseBody$ |
| Session name | bpSession |
Leggi i singoli campi
Azione JSON → Get single node value:
| Campo | Valore |
|---|---|
| Session name | bpSession |
| Node path | $.data.id |
| Output | vNoteId (String) |
Altri percorsi utili:
| Percorso | Contenuto |
|---|---|
$.data.sessionId | Identificativo della sessione |
$.data.client.displayName | Nome del cliente |
$.data.reference | Riferimento della prenotazione |
$.data.classification.category | Categoria del documento |
$.pagination.nextCursor | Cursore 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:
| Campo | Valore |
|---|---|
| Source | $vResponse{statusCode}$ |
| Condition | Greater than or equal to |
| Value | 400 |
Nel ramo positivo lancia un'eccezione con Error handling → Throw, messaggio
Errore API Blu Pantheon: $vResponse{statusCode}$.
Implementa il backoff sul 429
| Azione | Configurazione |
|---|---|
| If | $vResponse{statusCode}$ uguale a 429 |
| String → Assign | vRetryAfter = $vResponse{headers}{Retry-After}$ |
| Delay | $vRetryAfter$ secondi |
| Loop | ripeti 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.
| Codice | Azione consigliata |
|---|---|
401 | Throw immediato, nessun ritentativo. Notifica il team: la chiave è scaduta o revocata. |
403 | Throw immediato. Scope insufficienti sulla chiave. |
404 | Verifica il valore di vSessionId. Non ritentare. |
409 | Leggi la risorsa esistente e prosegui: l'operazione era già stata eseguita. |
429 | Attendi Retry-After e ritenta fino a 3 volte. |
5xx | Ritenta 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.
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:
| Componente | Ruolo |
|---|---|
| Listener | Verifica firma, accoda, risponde 202 |
| Coda (RabbitMQ, Service Bus, tabella SQL) | Assorbe i picchi |
| Dispatcher | Legge dalla coda e chiama deploy rispettando la concorrenza massima |
| Work Queue di A360 | Alternativa nativa, con priorità e ritentativi integrati |
Leggere gli input nel bot
Nel Bot Editor definisci le variabili di input con Use as → Input:
| Nome | Tipo |
|---|---|
vEventType | String |
vDeliveryId | String |
vEventId | String |
vPayload | String |
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.