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:

1{
2 "type": "asset.created",
3 "resource": {
4 "type": "asset",
5 "id": "<asset-id>"
6 },
7 "user": {
8 "id": "<user-id>"
9 },
10 "team": {
11 "id": "<team-id>"
12 }
13}

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
1import hmac
2import hashlib
3
4def verify_signature(curr_time, req_time, signature, body, secret):
5 """
6 Verify Webhook signature
7 :Args:
8 curr_time (float): Current epoch time
9 req_time (float): Request epoch time
10 signature (str): Signature provided by the Frame.io API for the given request
11 body (str): Webhook body from the received POST
12 secret (str): The secret for this Webhook that you saved when you first created it
13 """
14 if int(curr_time) - int(req_time) < 500:
15 message = 'v0:{}:{}'.format(req_time, body)
16 calculated_signature = 'v0={}'.format(hmac.new(
17 bytes(secret, 'latin-1'),
18 msg=bytes(message, 'latin-1'),
19 digestmod=hashlib.sha256).hexdigest())
20 if calculated_signature == signature:
21 return True
22 return False
1const crypto = require('crypto');
2
3// Capture the signature, secret, timestamp and payload from a new webhook event:
4const
5signature = 'v0=a77ce6856e609c884575c2fd211d07a9ad1c3f72e19c06ff710e8f086ffca883',
6secret = 'yxSE59T0gtZOFZxw6UhLwTkhd2m8ntNSdSWnApQ0xOnMEzSoXbD8sGFP4bzb7MbS',
7timestamp = 1604004499, // UNIX timestamp in seconds
8payload = {
9 "project": {
10 "id": "f348e9f4-f142-42f9-b3bf-478d93f0feb4"
11 },
12 "resource": {
13 "id": "6aad9151-c216-4d6f-b5e9-530df551a426",
14 "type": "asset"
15 },
16 "team": {
17 "id": "aa891687-4b1e-4150-9b6d-9e4911c5b436"
18 },
19 "type": "asset.label.updated",
20 "user": {
21 "id": "59c9ade1-311b-4c3b-8231-b9d88e9a1a85"
22 }
23},
24body = JSON.stringify(payload),
25
26// Validate that caught payload is not older than 5 minutes
27currentTimeUTC = (new Date()).getTime(),
28currentTimestamp = currentTimeUTC / 1000, // JavaScript uses milliseconds whereas Unix Time is in seconds.
29minutes = 5,
30expired = (currentTimestamp - timestamp) > minutes*60
31hmac1 = crypto.createHmac('sha256', secret),
32generateSignature = hmac1.update(`v0:${timestamp}:${body}`).digest('hex')
33
34// Evaluates to true if the webhook is verified
35console.log(!expired && signature === `v0=${generateSignature}`)
1// Full Go sample code: https://github.com/Frameio/webhooks-example-app/blob/master/main.go
2
3func handler(w http.ResponseWriter, r *http.Request) {
4 out, err := httputil.DumpRequest(r, true)
5 if err != nil {
6 w.WriteHeader(http.StatusInternalServerError)
7 return
8 }
9
10 log.Println(string(out))
11
12 // Verify the message has been delivered in the last 5 minutes.
13 timestampStr := r.Header.Get("X-Frameio-Request-Timestamp")
14 timestamp, err := strconv.ParseInt(timestampStr, 10, 64)
15 if err != nil {
16 w.WriteHeader(http.StatusBadRequest)
17 return
18 }
19
20 if time.Since(time.Unix(timestamp, 0)) > 5*time.Minute {
21 w.WriteHeader(http.StatusBadRequest)
22 return
23 }
24
25 // Verify request signature.
26 expected := r.Header.Get("X-Frameio-Signature")
27 signature, _ := computeSignature(r, timestamp, secretKey)
28 if expected != signature {
29 w.WriteHeader(http.StatusUnauthorized)
30 return
31 }
32
33 var event *Event
34 decoder := json.NewDecoder(r.Body)
35 err = decoder.Decode(&event)
36 if err != nil {
37 w.WriteHeader(http.StatusInternalServerError)
38 return
39 }
40
41 // Handle webhook here.
42 log.Println(event.ID)
43
44 w.WriteHeader(http.StatusOK)
45}
46
47// The request includes headers to enable the recipient to validate
48// that the request is from Frame.io and that it's been delivered within
49// the expected time range. To learn more about how this works, take a
50// look at our docs https://docs.frame.io/docs/webhooks#section-security.
51func computeSignature(r *http.Request, timestamp int64, secret string) (string, error) {
52 body, err := ioutil.ReadAll(r.Body)
53 if err != nil {
54 return "", err
55 }
56 copy := body[:]
57 r.Body = ioutil.NopCloser(bytes.NewReader(copy))
58
59 msg := fmt.Sprintf("%s:%d:%s", version, timestamp, string(body))
60
61 key := []byte(secret)
62 h := hmac.New(sha256.New, key)
63 h.Write([]byte(msg))
64
65 result := fmt.Sprintf("%s=%s", version, hex.EncodeToString(h.Sum(nil)))
66
67 return result, nil
68}