Présentation des webhooks

Exemple d'application

Si vous souhaitez créer votre propre consommateur pour les webhooks Frame.io, n’hésitez pas à récupérer et étendre notre exemple d’application sur Github.

Introduction

Les webhooks permettent de tirer profit des événements qui se produisent dans Frame.io en les transformant en notification qui peuvent être envoyées à des systèmes externes pour traitement, rappels d’API et, au final, automatisation de workflow.

Configuration

Les webhooks peuvent être configurés dans la zone Webhooks de notre site développeur. Un webhook nécessite :

  • Nom — Sera affiché uniquement sur le site développeur.
  • URL — Où livrer les événements.
  • Équipe — À quelle équipe ce webhook sera ajouté.
  • Événements — Quel événement ou quels événements doivent déclencher le webhook.

Événements pris en charge

Un seul webhook peut s’abonner à n’importe quel nombre des événements suivants :

Projets

ÉvénementDéclencheur
project.createdUn nouveau Projet est créé
project.updatedLes Paramètres d’un Projet sont mis à jour
project.deletedUn Projet est supprimé

Ressources

ÉvénementDéclencheur
asset.createdUne ressource est d’abord ajoutée/créée dans Frame.io, mais probablement avant que la ressource soit entièrement chargée
asset.copiedUn asset a été copié
asset.updatedLa description, le nom ou d’autres informations sur le fichier d’un asset sont modifiés
asset.deletedUn asset est supprimé (manuellement ou autrement)
asset.readyTous les transcodages sont terminés, après qu’un asset a été chargé et traité
asset.label.updatedLe libellé de statut d’un asset est défini, modifié ou supprimé
asset.versionedUn asset fait l’objet d’un contrôle de version
Contrôle de version des asset

Lorsque l’événement asset.versioned se déclenche, vous recevrez une charge utile avec l’ID de l’asset qui a fait l’objet d’un contrôle de version, et non la pile de versions elle-même. Donc, si vous comptez transmettre cet id à une autre fonction en pensant qu’il s’agit de l’ID de la pile de versions, vous devrez d’abord rechercher et localiser cette ressource « parent » particulière.

Mises à jour des libellés d'asset

L’événement asset.label.updated ne se déclenchera pas lorsque le libellé de statut est modifié via un appel PUT au point d’entrée /v2/assets/:id via l’API publique (BES-408). Il se déclenchera cependant lorsque le libellé de statut est mis à jour à l’aide d’applications et d’intégrations Frame.io natives (web, iOS, Premiere, After Effects, FCPX, etc.).

Commentaires

ÉvénementDéclencheur
comment.createdUn nouveau commentaire ou une nouvelle réponse est créé(e)
comment.updatedUn commentaire est modifié
comment.deletedUn commentaire est supprimé
comment.completedUn commentaire est terminé
comment.uncompletedUn commentaire n’est pas terminé

Liens de révision

ÉvénementDéclencheur
reviewlink.createdUn nouveau lien de révision est créé

Collaborateurs

ÉvénementDéclencheur
collaborator.createdUn utilisateur collaborateur a été ajouté à votre compte
collaborator.deletedUn utilisateur collaborateur a été supprimé de votre compte

Membres de l’équipe

ÉvénementDéclencheur
teammember.createdUn membre de l’équipe a été ajouté à votre compte
teammember.deletedUn membre de l’équipe a été supprimé de votre compte

Charge utile

Frame.io fournit une charge utile JSON au point d’entrée webhook spécifié.Voici un exemple de charge utile pour un événement 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}

Toutes les charges utiles contiennent un champ type, indiquant le type d’événement qui se produit, ainsi qu’un objet resource.L’objet resource spécifie le type et l’id de la ressource liée à cet événement.Dans l’exemple ci-dessus d’un événement asset.created, il s’agirait de l’id pour le fichier nouvellement créé.De plus, les objets user et team sont inclus.Ils font référence à l’utilisateur qui a déclenché l’événement et au contexte d’équipe pour la ressource.En dehors du contexte immédiat de l’utilisateur et de l’équipe, nous n’incluons aucune information supplémentaire sur la ressource abonnée. Si votre application nécessite des informations ou un contexte supplémentaires, nous recommandons d’utiliser notre API HTTP pour effectuer des requêtes de suivi.

Nouvelles tentatives

Si une erreur (réponse avec un code d’état différent de 200) ou un délai d’expiration se produit lors de la diffusion du webhook vers votre service, la charge utile fera l’objet de trois nouvelles tentatives, pour un total de quatre tentatives de diffusion.

Sécurité

Par défaut, tous les webhooks sont fournis avec une clé de signature. Ceci n’est pas configurable. Cette clé peut être utilisée pour vérifier que la requête provient de Frame.io.

Vérifier les signatures de webhook

Pour protéger une intégration contre les attaques man-in-the-middle et de relecture, il est essentiel de vérifier les signatures de webhook. La vérification garantit que les charges utiles de webhook ont effectivement été envoyées par Frame.io et que le contenu de la charge utile n’a pas été modifié lors du transport.

Inclus dans la requête POST se trouvent les en-têtes suivants :

NomDescription
X-Frameio-Request-TimestampL’heure de diffusion du webhook
X-Frameio-SignatureLa signature calculée
La date et l’heure correspond au moment de la diffusion depuis les systèmes de Frame.io. Ceci peut être utilisé pour empêcher les attaques de relecture. Nous recommandons de vérifier que cette heure se situe dans les 5 minutes de l’heure locale. La signature est un hachage HMAC SHA256 utilisant la clé de signature fournie lors de la création initiale du webhook.

Suivez ces étapes pour vérifier la signature :

  1. Extrayez la signature des en-têtes HTTP
  2. Créez un message à signer en combinant la version, l’heure de diffusion et le corps de la requête : v0:timestamp:body
  3. Calculez la signature HMAC SHA256 en utilisant votre secret de signature. Remarque : La signature fournie est préfixée par v0=. Actuellement, Frame.io n’a que cette version pour signer les requêtes. Assurez-vous que ce préfixe est ajouté au début de la signature calculée.
  4. Comparez !
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}