Blu Pantheon
الأتمتة وRPAمنصات RPA

cURL e terminale

Smoke test end-to-end da terminale: autenticazione, scritture idempotenti, header di rate limit e verifica firma con openssl.

Prima di scrivere un workflow complesso, verifica il contratto dal terminale. Questa pagina contiene i comandi essenziali per validare credenziali, scritture, limiti di frequenza e firma HMAC.

Prima di iniziare

  • curl 8.x, jq, openssl e uuidgen disponibili nel PATH
  • La chiave API bp_live_xxxxxxxxxxxxxxxx
  • Il segreto whsec_xxxxxxxxxxxxxxxx
  • Una shell bash o zsh

Preparazione dell'ambiente

export BP_API_BASE_URL="https://api.blupantheon.com"
export BP_API_KEY="bp_live_xxxxxxxxxxxxxxxx"
export BP_PROJECT_ID="bp_proj_7f3a9c21"
export BP_WEBHOOK_SECRET="whsec_xxxxxxxxxxxxxxxx"

Cronologia della shell

Esportare il segreto in una variabile lo salva nella cronologia del terminale. Anteponi uno spazio al comando (in bash con HISTCONTROL=ignorespace) oppure caricalo da file con source .env.local e aggiungi quel file a .gitignore.

1. Verificare l'autenticazione

curl -sS "$BP_API_BASE_URL/identity/v1/me" \
  -H "Authorization: Bearer $BP_API_KEY" \
  -H "Accept: application/json" | jq

Risposta attesa:

{
  "data": {
    "tenantId": "bp_ten_4a91c0e5",
    "tenantName": "ACME S.p.A.",
    "keyPrefix": "bp_live_",
    "keyLast4": "9f2c",
    "scopes": [
      "identity:sessions:read",
      "identity:sessions:write",
      "identity:clients:read",
      "identity:clients:write",
      "identity:bookings:read"
    ],
    "expiresAt": "2027-09-04T00:00:00.000Z"
  }
}
Codice ricevutoSignificato
200Chiave valida. Controlla scopes e expiresAt.
401Chiave errata, scaduta o revocata.
403Chiave valida ma senza permessi sull'endpoint.

2. Elencare le risorse

curl -sS -G "$BP_API_BASE_URL/identity/v1/clients" \
  -H "Authorization: Bearer $BP_API_KEY" \
  -H "X-BP-Project: $BP_PROJECT_ID" \
  --data-urlencode "limit=5" \
  --data-urlencode "sort=-createdAt" | jq '.data[] | {id, displayName, email}'

Paginazione con cursore:

NEXT_CURSOR="$(curl -sS -G "$BP_API_BASE_URL/identity/v1/clients" \
  -H "Authorization: Bearer $BP_API_KEY" \
  -H "X-BP-Project: $BP_PROJECT_ID" \
  --data-urlencode "limit=50" | jq -r '.pagination.nextCursor // empty')"

if [ -n "$NEXT_CURSOR" ]; then
  curl -sS -G "$BP_API_BASE_URL/identity/v1/clients" \
    -H "Authorization: Bearer $BP_API_KEY" \
    -H "X-BP-Project: $BP_PROJECT_ID" \
    --data-urlencode "limit=50" \
    --data-urlencode "cursor=$NEXT_CURSOR" | jq '.data | length'
fi

3. Creare una risorsa con Idempotency-Key

IDEMPOTENCY_KEY="$(uuidgen | tr 'A-Z' 'a-z')"

curl -sS -X POST "$BP_API_BASE_URL/identity/v1/clients" \
  -H "Authorization: Bearer $BP_API_KEY" \
  -H "X-BP-Project: $BP_PROJECT_ID" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  --data-raw '{
    "type": "azienda",
    "displayName": "ACME Impianti S.r.l.",
    "legalName": "ACME Impianti S.r.l.",
    "vatNumber": "IT01234567890",
    "email": "amministrazione@acme.it",
    "phone": "+39 02 1234567",
    "address": {
      "street": "Via Torino 42",
      "city": "Milano",
      "province": "MI",
      "postalCode": "20123",
      "country": "IT"
    }
  }' | jq

Ripeti lo stesso comando con la stessa Idempotency-Key: ottieni la stessa risposta e nessun cliente duplicato.

# Seconda esecuzione: stesso risultato, nessuna creazione
curl -sS -o /tmp/bp-second.json -w "HTTP %{http_code}\n" \
  -X POST "$BP_API_BASE_URL/identity/v1/clients" \
  -H "Authorization: Bearer $BP_API_KEY" \
  -H "X-BP-Project: $BP_PROJECT_ID" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  --data-raw '{"type":"azienda","displayName":"ACME Impianti S.r.l.","vatNumber":"IT01234567890"}'

jq -r '.data.id' /tmp/bp-second.json

Durata della chiave di idempotenza

Le chiavi di idempotenza sono valide 24 ore. Oltre quel periodo la stessa chiave produce una nuova risorsa. Genera chiavi derivate da un identificativo stabile del tuo processo, ad esempio X-BP-Delivery.

4. Ispezionare gli header di rate limit

curl -sS -D - -o /dev/null \
  "$BP_API_BASE_URL/identity/v1/clients?limit=1" \
  -H "Authorization: Bearer $BP_API_KEY" \
  -H "X-BP-Project: $BP_PROJECT_ID" \
  | grep -i "x-ratelimit\|retry-after"

Output tipico:

x-ratelimit-limit: 600
x-ratelimit-remaining: 587
x-ratelimit-reset: 1767225660
HeaderSignificato
X-RateLimit-LimitRichieste consentite nella finestra corrente
X-RateLimit-RemainingRichieste ancora disponibili
X-RateLimit-ResetIstante Unix di azzeramento del contatore
Retry-AfterPresente solo con 429. Secondi da attendere.

Script che rispetta il limite:

#!/usr/bin/env bash
set -euo pipefail

call_api() {
  local path="$1"
  local headers_file
  headers_file="$(mktemp)"

  local status
  status="$(curl -sS -D "$headers_file" -o /tmp/bp-body.json -w "%{http_code}" \
    "$BP_API_BASE_URL$path" \
    -H "Authorization: Bearer $BP_API_KEY" \
    -H "X-BP-Project: $BP_PROJECT_ID")"

  if [ "$status" = "429" ]; then
    local wait_seconds
    wait_seconds="$(grep -i '^retry-after:' "$headers_file" | tr -d '\r' | awk '{print $2}')"
    echo "Limite raggiunto, attendo ${wait_seconds:-30}s" >&2
    sleep "${wait_seconds:-30}"
    rm -f "$headers_file"
    call_api "$path"
    return
  fi

  local remaining
  remaining="$(grep -i '^x-ratelimit-remaining:' "$headers_file" | tr -d '\r' | awk '{print $2}')"
  if [ -n "$remaining" ] && [ "$remaining" -lt 20 ]; then
    echo "Attenzione: solo $remaining richieste residue" >&2
  fi

  rm -f "$headers_file"
  cat /tmp/bp-body.json
}

call_api "/identity/v1/clients?limit=5" | jq '.data | length'

5. Inviare un webhook di prova

ConsoleConfigurazioneWebhookInvia evento di prova

Seleziona l'endpoint, scegli l'evento webhook.test e clicca Invia. Il pannello mostra subito codice di risposta, tempo di risposta e corpo restituito dal tuo endpoint.

Puoi anche scatenare il test via API:

curl -sS -X POST \
  "$BP_API_BASE_URL/identity/v1/webhooks/wh_5f2a91c0/test" \
  -H "Authorization: Bearer $BP_API_KEY" \
  -H "X-BP-Project: $BP_PROJECT_ID" \
  -H "Content-Type: application/json" \
  --data-raw '{"event": "webhook.test"}' | jq

6. Calcolare una firma con openssl

TIMESTAMP="$(date +%s)"
BODY='{"id":"evt_test","event":"webhook.test","apiVersion":"2026-09-01","createdAt":"2026-09-04T09:00:00.000Z","tenantId":"bp_ten_4a91c0e5","projectId":"bp_proj_7f3a9c21","data":{}}'

SIGNATURE="$(printf '%s.%s' "$TIMESTAMP" "$BODY" \
  | openssl dgst -sha256 -hmac "$BP_WEBHOOK_SECRET" -r \
  | cut -d' ' -f1)"

echo "X-BP-Signature: t=$TIMESTAMP,v1=$SIGNATURE"

Usa printf, non echo

echo aggiunge un ritorno a capo finale che altera la stringa firmata e produce una firma diversa. Usa sempre printf senza \n.

7. Inviare un webhook firmato al tuo endpoint

#!/usr/bin/env bash
set -euo pipefail

ENDPOINT="${1:-http://localhost:3000/webhooks/blupantheon}"
TIMESTAMP="$(date +%s)"
DELIVERY_ID="$(uuidgen | tr 'A-Z' 'a-z')"

BODY="$(cat <<'JSON'
{"id":"evt_01J9X5B0PZQ4T1N7C3H8M2AKE","event":"session.handover.requested","apiVersion":"2026-09-01","createdAt":"2026-09-04T09:26:31.884Z","tenantId":"bp_ten_4a91c0e5","projectId":"bp_proj_7f3a9c21","data":{"sessionId":"bp_sess_3d81f0aa","reason":"richiesta_esplicita","priority":"alta","queue":"assistenza-tecnica","client":{"id":"bp_cli_82ae4f10","displayName":"Mario Rossi","email":"mario.rossi@example.it"},"summary":"Il cliente chiede un intervento tecnico.","traceId":"trc_6b19e0d4c8f24a7f"}}
JSON
)"

SIGNATURE="$(printf '%s.%s' "$TIMESTAMP" "$BODY" \
  | openssl dgst -sha256 -hmac "$BP_WEBHOOK_SECRET" -r \
  | cut -d' ' -f1)"

curl -sS -i -X POST "$ENDPOINT" \
  -H "Content-Type: application/json; charset=utf-8" \
  -H "User-Agent: BluPantheon-Webhooks/1.0" \
  -H "X-BP-Event: session.handover.requested" \
  -H "X-BP-Delivery: $DELIVERY_ID" \
  -H "X-BP-Timestamp: $TIMESTAMP" \
  -H "X-BP-Attempt: 1" \
  -H "X-BP-Api-Version: 2026-09-01" \
  -H "X-BP-Signature: t=$TIMESTAMP,v1=$SIGNATURE" \
  --data-raw "$BODY"

Test negativi da eseguire subito dopo:

# Firma alterata: atteso 401
SIGNATURE_ERRATA="$(printf '%s' "$SIGNATURE" | tr '0-9a-f' '1-9a-f0')"
curl -sS -o /dev/null -w "firma alterata -> %{http_code}\n" -X POST "$ENDPOINT" \
  -H "Content-Type: application/json" \
  -H "X-BP-Delivery: $(uuidgen | tr 'A-Z' 'a-z')" \
  -H "X-BP-Timestamp: $TIMESTAMP" \
  -H "X-BP-Signature: t=$TIMESTAMP,v1=$SIGNATURE_ERRATA" \
  --data-raw "$BODY"

# Timestamp vecchio: atteso 400
OLD_TIMESTAMP="$(( TIMESTAMP - 900 ))"
OLD_SIGNATURE="$(printf '%s.%s' "$OLD_TIMESTAMP" "$BODY" \
  | openssl dgst -sha256 -hmac "$BP_WEBHOOK_SECRET" -r | cut -d' ' -f1)"
curl -sS -o /dev/null -w "timestamp vecchio -> %{http_code}\n" -X POST "$ENDPOINT" \
  -H "Content-Type: application/json" \
  -H "X-BP-Delivery: $(uuidgen | tr 'A-Z' 'a-z')" \
  -H "X-BP-Timestamp: $OLD_TIMESTAMP" \
  -H "X-BP-Signature: t=$OLD_TIMESTAMP,v1=$OLD_SIGNATURE" \
  --data-raw "$BODY"

# Header firma assente: atteso 401
curl -sS -o /dev/null -w "firma assente -> %{http_code}\n" -X POST "$ENDPOINT" \
  -H "Content-Type: application/json" \
  -H "X-BP-Delivery: $(uuidgen | tr 'A-Z' 'a-z')" \
  --data-raw "$BODY"

8. Script di verifica di una firma ricevuta

Utile per analizzare una consegna estratta dai log della Console.

verifica-firma.sh
#!/usr/bin/env bash
set -euo pipefail

# Uso: ./verifica-firma.sh corpo.json "t=1767225600,v1=9f2c..." "whsec_xxxxxxxxxxxxxxxx"

BODY_FILE="${1:?indicare il file con il corpo grezzo}"
SIGNATURE_HEADER="${2:?indicare il valore di X-BP-Signature}"
SECRET="${3:-$BP_WEBHOOK_SECRET}"
TOLERANCE=300

TIMESTAMP="$(printf '%s' "$SIGNATURE_HEADER" | tr ',' '\n' \
  | grep '^t=' | head -n 1 | cut -d= -f2)"

if [ -z "$TIMESTAMP" ]; then
  echo "ESITO: timestamp assente nell header" >&2
  exit 2
fi

NOW="$(date +%s)"
AGE=$(( NOW - TIMESTAMP ))
AGE_ABS=${AGE#-}

if [ "$AGE_ABS" -gt "$TOLERANCE" ]; then
  echo "ESITO: timestamp fuori tolleranza (differenza ${AGE_ABS}s)" >&2
  exit 3
fi

EXPECTED="$(printf '%s.' "$TIMESTAMP" | cat - "$BODY_FILE" \
  | openssl dgst -sha256 -hmac "$SECRET" -r | cut -d' ' -f1)"

MATCH=0
while IFS= read -r candidate; do
  if [ "$candidate" = "$EXPECTED" ]; then
    MATCH=1
    break
  fi
done < <(printf '%s' "$SIGNATURE_HEADER" | tr ',' '\n' | grep '^v1=' | cut -d= -f2)

if [ "$MATCH" -eq 1 ]; then
  echo "ESITO: firma valida"
  echo "  timestamp: $TIMESTAMP (età ${AGE_ABS}s)"
  echo "  attesa:    $EXPECTED"
  exit 0
fi

echo "ESITO: firma NON valida" >&2
echo "  attesa:   $EXPECTED" >&2
echo "  ricevute: $(printf '%s' "$SIGNATURE_HEADER" | tr ',' ' ')" >&2
exit 1

Uso:

chmod +x verifica-firma.sh
./verifica-firma.sh corpo.json "t=1767225600,v1=9f2c1e7b40a3d6c5b8e1f0a2d4c7b9e3f5a8c1d0e2b4f6a9c3d5e7b1f0a2c41d"

Il file del corpo deve essere byte-identico

Salva il corpo con curl -o o con la funzione Scarica corpo grezzo dei log consegne. Non copiarlo dal browser: verrebbero aggiunti ritorni a capo o rientri che invalidano la firma.

9. Smoke test completo

smoke-test.sh
#!/usr/bin/env bash
set -euo pipefail

: "${BP_API_KEY:?BP_API_KEY non impostata}"
: "${BP_PROJECT_ID:?BP_PROJECT_ID non impostata}"
BASE="${BP_API_BASE_URL:-https://api.blupantheon.com}"

pass=0
fail=0

check() {
  local label="$1"
  local expected="$2"
  local actual="$3"
  if [ "$actual" = "$expected" ]; then
    printf 'OK   %-40s (%s)\n' "$label" "$actual"
    pass=$(( pass + 1 ))
  else
    printf 'KO   %-40s atteso %s, ricevuto %s\n' "$label" "$expected" "$actual"
    fail=$(( fail + 1 ))
  fi
}

status() {
  curl -sS -o /dev/null -w "%{http_code}" "$@"
}

check "autenticazione valida" "200" \
  "$(status "$BASE/identity/v1/me" -H "Authorization: Bearer $BP_API_KEY")"

check "chiave errata respinta" "401" \
  "$(status "$BASE/identity/v1/me" -H "Authorization: Bearer bp_live_nonvalida")"

check "chiave assente respinta" "401" \
  "$(status "$BASE/identity/v1/me")"

check "lettura clienti" "200" \
  "$(status "$BASE/identity/v1/clients?limit=1" \
      -H "Authorization: Bearer $BP_API_KEY" \
      -H "X-BP-Project: $BP_PROJECT_ID")"

check "risorsa inesistente" "404" \
  "$(status "$BASE/identity/v1/clients/bp_cli_inesistente" \
      -H "Authorization: Bearer $BP_API_KEY" \
      -H "X-BP-Project: $BP_PROJECT_ID")"

echo
echo "Risultato: $pass superati, $fail falliti"
[ "$fail" -eq 0 ]

Diagnostica di rete

# Verifica del certificato TLS del tuo endpoint
openssl s_client -connect automation.acme.it:443 -servername automation.acme.it </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -issuer -dates

# Verifica assenza di redirect
curl -sS -o /dev/null -w "%{http_code} -> %{redirect_url}\n" \
  -X POST "https://automation.acme.it/webhooks/blupantheon" \
  -H "Content-Type: application/json" --data-raw '{}'

# Misura del tempo di risposta
curl -sS -o /dev/null -w "connessione %{time_connect}s, TLS %{time_appconnect}s, totale %{time_total}s\n" \
  -X POST "https://automation.acme.it/webhooks/blupantheon" \
  -H "Content-Type: application/json" --data-raw '{}'

Soglia da rispettare

Il valore time_total deve restare ben sotto i 10 secondi anche sotto carico. Se supera i 2 secondi, sposta l'elaborazione in una coda asincrona.

Passi successivi

في هذه الصفحة