Panoramica sui webhook

Applicazione di esempio

Se vuoi creare un consumer personale per i webhook di Frame.io, puoi scaricare ed estendere la nostra app di esempio su GitHub.

Introduzione

I webhook consentono di sfruttare gli eventi che si verificano all’interno di Frame.io trasformandoli in notifiche che possono essere inviate a sistemi esterni per l’elaborazione, i callback API e l’automazione dei flussi di lavoro.

Configurazione

I webhook possono essere configurati nella sezione Webhooks del nostro sito per sviluppatori. Un webhook richiede:

  • Nome: viene visualizzato solo nel sito per sviluppatori.
  • URL: dove consegnare gli eventi.
  • Team: a quale team verrà aggiunto il webhook.
  • Eventi: l’evento o gli eventi che devono attivare il webhook.

Eventi supportati

Un singolo webhook può sottoscrivere qualsiasi numero dei seguenti eventi:

Progetti

EventoTrigger
project.createdViene creato un nuovo progetto
project.updatedVengono aggiornate le impostazioni di un progetto
project.deletedViene eliminato un progetto

Risorse

EventoTrigger
asset.createdViene aggiunta o creata per la prima volta una risorsa in Frame.io, ma probabilmente prima che la risorsa sia completamente caricata
asset.copiedÈ stata copiata una risorsa
asset.updatedLa descrizione, il nome o altre informazioni di file di una risorsa vengono modificati
asset.deletedUna risorsa viene eliminata (manualmente o in altro modo)
asset.readyTutte le transcodifiche sono state completate, dopo che una risorsa è stata caricata ed elaborata
asset.label.updatedL’etichetta di stato di una risorsa viene impostata, cambiata o rimossa
asset.versionedUna risorsa viene sottoposta al controllo delle versioni
Controllo delle versioni delle risorse

Quando viene attivato l’evento asset.versioned, ricevi un payload con l’ID della risorsa di cui è stato eseguito il controllo delle versioni, non dello stack di versioni stesso. Quindi, se pensi di poter passare questo id a un’altra funzione pensando che sia l’ID dello stack di versioni, devi prima cercare e rintracciare quella particolare risorsa “padre”.

Aggiornamenti delle etichette delle risorse

L’evento asset.label.updated non si attiva quando l’etichetta di stato viene cambiata tramite una chiamata PUT all’endpoint /v2/assets/:id tramite l’API pubblica (BES-408). Si attiva, invece, quando l’etichetta di stato viene aggiornata utilizzando app e integrazioni native di Frame.io (web, iOS, Premiere, After Effects, FCPX ecc.).

Commenti

EventoTrigger
comment.createdViene creato un nuovo commento o una nuova risposta
comment.updatedUn commento viene modificato
comment.deletedUn commento viene eliminato
comment.completedUn commento viene completato
comment.uncompletedIl completamento di un commento viene annullato
EventoTrigger
reviewlink.createdViene creato un nuovo link di revisione

Collaboratori

EventoTrigger
collaborator.createdÈ stato aggiunto un collaboratore al tuo account
collaborator.deletedÈ stato rimosso un collaboratore dal tuo account

Membri del team

EventoTrigger
teammember.createdÈ stato aggiunto un membro del team al tuo account
teammember.deletedÈ stato rimosso un membro del team dal tuo account

Payload

Frame.io fornisce un payload JSON all’endpoint del webhook specificato. Ecco un esempio di payload per un evento asset.created:

{
"type": "asset.created",
"resource": {
"type": "asset",
"id": "<asset-id>"
},
"user": {
"id": "<user-id>"
},
"team": {
"id": "<team-id>"
}
}

Tutti i payload contengono un campo type, che indica il tipo di evento in corso, oltre a un oggetto resource. L’oggetto resource specifica il tipo (type) e l’id della risorsa correlata a questo evento. Nell’esempio precedente di un evento asset.created, questo sarebbe l’id della risorsa appena creata. Inoltre, sono inclusi gli oggetti user e team. Questi fanno riferimento all’utente che ha attivato l’evento e al contesto del team per la risorsa. Oltre al contesto immediato di utente e team, non includiamo informazioni aggiuntive sulla risorsa sottoscritta. Se l’applicazione richiede informazioni o contesto aggiuntivi, consigliamo di utilizzare la nostra API HTTP per effettuare richieste di follow-up.

Nuovi tentativi

Se si verifica un errore (risposta con codice di stato diverso da 200) o un timeout durante la consegna del webhook al servizio, il payload viene ripetuto tre volte, per un totale di quattro tentativi di consegna.

Sicurezza

Per impostazione predefinita, tutti i webhook sono forniti con una chiave di firma. Non è configurabile. Questa chiave può essere utilizzata per verificare che la richiesta provenga da Frame.io.

Verifica delle firme dei webhook

Per proteggere un’integrazione dagli attacchi man-in-the-middle e replay, è essenziale verificare le firme dei webhook. La verifica garantisce che i payload dei webhook siano stati effettivamente inviati da Frame.io e che il contenuto del payload non sia stato modificato durante il trasporto.

Nella richiesta POST sono incluse le seguenti intestazioni:

NomeDescrizione
X-Frameio-Request-TimestampL’orario di consegna del webhook
X-Frameio-SignatureLa firma calcolata
La marca temporale è l’orario di consegna dai sistemi di Frame.io. Può essere utilizzata per evitare attacchi di tipo replay. Si consiglia di verificare che questo orario rientri nei 5 minuti dall’orario locale. La firma è un hash HMAC SHA256 che utilizza la chiave di firma fornita quando il webhook viene creato per la prima volta.

Segui questi passaggi per verificare la firma:

  1. Estrai la firma dalle intestazioni HTTP
  2. Crea un messaggio da firmare combinando la versione, l’ora di consegna e il corpo della richiesta: v0:timestamp:body
  3. Calcola la firma HMAC SHA256 utilizzando il secret di firma. Nota: la firma fornita ha il prefisso v0=. Al momento Frame.io ha solo questa versione per firmare le richieste. Assicurati che questo prefisso venga anteposto alla firma calcolata.
  4. Confronta!
Python
import hmac
import hashlib
def verify_signature(curr_time, req_time, signature, body, secret):
"""
Verify Webhook signature
:Args:
curr_time (float): Current epoch time
req_time (float): Request epoch time
signature (str): Signature provided by the Frame.io API for the given request
body (str): Webhook body from the received POST
secret (str): The secret for this Webhook that you saved when you first created it
"""
if int(curr_time) - int(req_time) < 500:
message = 'v0:{}:{}'.format(req_time, body)
calculated_signature = 'v0={}'.format(hmac.new(
bytes(secret, 'latin-1'),
msg=bytes(message, 'latin-1'),
digestmod=hashlib.sha256).hexdigest())
if calculated_signature == signature:
return True
return False
const crypto = require('crypto');
// Capture the signature, secret, timestamp and payload from a new webhook event:
const
signature = 'v0=a77ce6856e609c884575c2fd211d07a9ad1c3f72e19c06ff710e8f086ffca883',
secret = 'yxSE59T0gtZOFZxw6UhLwTkhd2m8ntNSdSWnApQ0xOnMEzSoXbD8sGFP4bzb7MbS',
timestamp = 1604004499, // UNIX timestamp in seconds
payload = {
"project": {
"id": "f348e9f4-f142-42f9-b3bf-478d93f0feb4"
},
"resource": {
"id": "6aad9151-c216-4d6f-b5e9-530df551a426",
"type": "asset"
},
"team": {
"id": "aa891687-4b1e-4150-9b6d-9e4911c5b436"
},
"type": "asset.label.updated",
"user": {
"id": "59c9ade1-311b-4c3b-8231-b9d88e9a1a85"
}
},
body = JSON.stringify(payload),
// Validate that caught payload is not older than 5 minutes
currentTimeUTC = (new Date()).getTime(),
currentTimestamp = currentTimeUTC / 1000, // JavaScript uses milliseconds whereas Unix Time is in seconds.
minutes = 5,
expired = (currentTimestamp - timestamp) > minutes*60
hmac1 = crypto.createHmac('sha256', secret),
generateSignature = hmac1.update(`v0:${timestamp}:${body}`).digest('hex')
// Evaluates to true if the webhook is verified
console.log(!expired && signature === `v0=${generateSignature}`)
// Full Go sample code: https://github.com/Frameio/webhooks-example-app/blob/master/main.go
func handler(w http.ResponseWriter, r *http.Request) {
out, err := httputil.DumpRequest(r, true)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
log.Println(string(out))
// Verify the message has been delivered in the last 5 minutes.
timestampStr := r.Header.Get("X-Frameio-Request-Timestamp")
timestamp, err := strconv.ParseInt(timestampStr, 10, 64)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
if time.Since(time.Unix(timestamp, 0)) > 5*time.Minute {
w.WriteHeader(http.StatusBadRequest)
return
}
// Verify request signature.
expected := r.Header.Get("X-Frameio-Signature")
signature, _ := computeSignature(r, timestamp, secretKey)
if expected != signature {
w.WriteHeader(http.StatusUnauthorized)
return
}
var event *Event
decoder := json.NewDecoder(r.Body)
err = decoder.Decode(&event)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
// Handle webhook here.
log.Println(event.ID)
w.WriteHeader(http.StatusOK)
}
// The request includes headers to enable the recipient to validate
// that the request is from Frame.io and that it's been delivered within
// the expected time range. To learn more about how this works, take a
// look at our docs https://docs.frame.io/docs/webhooks#section-security.
func computeSignature(r *http.Request, timestamp int64, secret string) (string, error) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
return "", err
}
copy := body[:]
r.Body = ioutil.NopCloser(bytes.NewReader(copy))
msg := fmt.Sprintf("%s:%d:%s", version, timestamp, string(body))
key := []byte(secret)
h := hmac.New(sha256.New, key)
h.Write([]byte(msg))
result := fmt.Sprintf("%s=%s", version, hex.EncodeToString(h.Sum(nil)))
return result, nil
}