> This page is for Plate-forme, version Hérité.
> For other versions, use one of these documentation indexes:
> - V4 (default): https://next.developer.frame.io/platform/v4/llms.txt
> - V4 expérimental: https://next.developer.frame.io/platform/v4-experimental/llms.txt
> - Hérité: https://next.developer.frame.io/platform/v2/llms.txt

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://next.developer.frame.io/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://next.developer.frame.io/_mcp/server.

# Présentation des webhooks

<Info title="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](https://github.com/Frameio/webhooks-example-app).
</Info>


## 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](https://developer.frame.io/app/webhooks). 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énement | Déclencheur |
| ---------- | ---------- |
| `project.created` | Un nouveau Projet est créé |
| `project.updated` | Les Paramètres d'un Projet sont mis à jour |
| `project.deleted` | Un Projet est supprimé |




### Ressources



| Événement | Déclencheur |
| ---------- | ---------- |
| `asset.created` | Une ressource est d'abord ajoutée/créée dans Frame.io, mais probablement avant que la ressource soit entièrement chargée |
| `asset.copied` | Un asset a été copié |
| `asset.updated` | La description, le nom ou d'autres informations sur le fichier d'un asset sont modifiés |
| `asset.deleted` | Un asset est supprimé (manuellement ou autrement) |
| `asset.ready` | Tous les transcodages sont terminés, après qu'un asset a été chargé et traité |
| `asset.label.updated` | Le libellé de statut d'un asset est défini, modifié ou supprimé |
| `asset.versioned` | Un asset fait l'objet d'un contrôle de version |



<Warning title="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.
</Warning>

<Warning title="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.).
</Warning>


### Commentaires



| Événement | Déclencheur |
| ---------- | ---------- |
| `comment.created` | Un nouveau commentaire ou une nouvelle réponse est créé(e) |
| `comment.updated` | Un commentaire est modifié |
| `comment.deleted` | Un commentaire est supprimé |
| `comment.completed` | Un commentaire est terminé |
| `comment.uncompleted` | Un commentaire n'est pas terminé |




### Liens de révision



| Événement | Déclencheur |
| ---------- | ---------- |
| `reviewlink.created` | Un nouveau lien de révision est créé |



### Collaborateurs




| Événement | Déclencheur |
| ---------- | ---------- |
| `collaborator.created` | Un utilisateur collaborateur a été ajouté à votre compte |
| `collaborator.deleted` | Un utilisateur collaborateur a été supprimé de votre compte |




### Membres de l’équipe



| Événement | Déclencheur |
| ---------- | ---------- |
| `teammember.created` | Un membre de l'équipe a été ajouté à votre compte |
| `teammember.deleted` | Un 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* :





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

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 :
| Nom | Description |
| ---------- | ---------- |
| `X-Frameio-Request-Timestamp` | L'heure de diffusion du webhook |
| `X-Frameio-Signature` | La 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`**

```python title="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
```





```js
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}`)
```





```go
// 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
}
```