Webhook configuration provides filtering controls to reduce unnecessary event traffic and improve delivery performance. You can select specific webhook events, filter by Workspace and Production Step, and configure settings through organized tabs.
Setting Up a Webhook
Creating a webhook is a two-step process. You'll first need to set up how you want your webhook to behave through CreativeForce - what events should it listen to. After that, you'll set up your server to receive and manage the payload.
To set up a webhook, go to the “Studio settings” page via the menu in the top right corner, and click on "Integration" and from there on the sub-menu "Webhooks":
From there, click "Add Webhook." Webhooks require a few configuration options before you can make use of them.
The webhook configuration screen is organized into three tabs: Settings, Filters, & Custom Headers.
Settings: Configure basic webhook details (Payload URL, Secret, Events)
Filters: Set up filtering by Workspace and Production Step.
Custom Headers: Add custom headers to webhook requests
We'll go through each of these in the sections below.
Settings Tab
Configuration Option | Notes |
Payload URL | The Payload URL is the URL of the server that will receive the webhook requests.
The URL can't contain: localhost, creativeforce.io, metadata.google, burpcollaborator.net, .local, .internal, ram.aliyuncs.com, fuf.me, localtest.me, ulh.us
Good Payload URL Example
Bad Payload URL Example: https://localhost:4567/payload http://localhost:8000/payload |
Secret Key | When your secret key is set, Creative Force uses it to create a hash signature with each payload.
This hash signature is passed along with each request in the headers as X-CF-Signature.
The format of the secret key is Base64 RFC 2045
For more information, see the section on validating webhooks further down in this article. |
Trigger Events | Events are at the core of webhooks. The webhooks fire whenever a certain action is taken, either by users or the Creative Force system.
Your server (payload URL) will intercept and act upon these.
You can select from 7 different event groups, shown in the next table. |
Events to Trigger Webhooks
Event Category | Available Events |
Product Events |
|
Sample Events |
|
User Events |
|
Task Events |
|
Production Events |
|
Job Events |
|
Session Events |
|
When you're finished, click Save. Now that you created the webhook, it's time to set up our server to test the webhook.
Filters Tab
The Filters tab allows you to refine when webhooks are triggered. Filtering by Workspace and Production Step reduces webhook volume, improves delivery performance, and minimizes load on receiving systems.
This is particularly useful in high-volume production environments.
Workspace
Filter webhook events by specific Workspaces. Only events from selected Workspaces will trigger the webhook.
Select one or multiple Workspaces from the list.
Enable 'All Workspace Access' to receive events from all Workspaces.
Production Step
For task-related events, filter by Production Step to control which task events trigger your webhook.
📝 Note: Production Step filtering is only available when Task Events are selected.
Available Production Steps:
Capture
Final Selection
Photo Review
Digital Processing
External Post
External Post QC
Internal Post
Internal Post QC
Post Review
Copywriting
Copywriting QC
Cloud Automation
Cloud Automation QC
Asset Delivery
Custom Headers Tab
Add custom HTTP headers to your webhook requests for additional authentication or routing needs.
Delivery Headers
HTTP POST payloads that are delivered to your webhook's configured URL endpoint will contain several special headers:
Header | Description |
X-CF-Event | Name of the event that triggered the delivery. For example “Task completed”, “User created”. |
X-CF-Signature | The HMAC hex digest of the response body. See the section about validating the webhook later in this document. |
Product Webhooks
Key | Type | Description |
Action |
String | The action that was performed.
Appears as one of the following:
|
Actor |
Object | The user or the actor that triggered the event.
Note: We will send the UserID only, not the user name. |
PayloadId | String | ID of the payload (GUID) |
EventGroupName | String | The event group triggered. Value: product |
EventDatetimeUtc | Long | Unix Timestamp (milliseconds) - the number of milliseconds since the Unix Epoch |
JobId | String | ID of the job (GUID) |
JobCode | String | Job code |
ClientId | String | ID of the client (GUID). |
ClientName | String | Client name |
ProductId | String | ID of the product (GUID) |
ProductCode | String | Product code |
ProductStatusId | Int | Product Unit Status ID |
ProductStatusName | String | Product Unit Status Name |
Webhook Payload Example:
When a user resets a Product, the webhook payload would appear as shown below.
{
"Action":"ProductReset",
"Actor":{
"UserId":"19e62847-f302-4e32-9e4a-b6d8c304fcbe"
},
"PayloadId":"c02f0f73-1706-4973-9a4d-38007d6685e9",
"EventGroupName":"product",
"EventDatetimeUtc":1595204418000,
"JobId":"d84eef10-c8a6-437a-9f95-0b58d2a3cb38",
"JobCode":"Summer 2020",
"ClientId":"2c856b2f-aded-4609-bf67-27009603b2b9",
"ClientName":"My Big client",
"ProductId":"7a782d57-b0bf-4743-8f02-2b1a1e229869",
"ProductCode":"AW2130146"
}Sample Webhooks
Key | Type | Description |
Action |
String | The action that was performed.
Appears as one of the following:
|
Actor |
Object | The user or the actor that triggered the event.
Note: We will send the UserID only, not the user name. |
PayloadId | String | ID of the payload (GUID) |
EventGroupName |
String | The event group triggered. Value: sample |
EventDatetimeUtc |
Long | Unix Timestamp (milliseconds) - the number of milliseconds since the Unix Epoch |
ClientID |
String | ID of the Client (Workspace) where the Sample originated |
SampleId | String | ID of the Sample (GUID) |
SampleCode | String | Sample Code |
LocationId | String | ID of the location (GUID) |
LocationName | String | Location name |
SubLocationId | String | ID of the sub-location (GUID) |
SubLocationName | String | Sub-location name |
Products |
Array | An array of ID of the product ID (GUID) and Product code.
If there is no product, then return an empty array.
products: [{productId: "", productCode: ""},....} |
Webhook Payload Example:
When a user checks in a Sample, the payload would appear as shown below.
{
"ClientId": "719bee19-f630-455a-8f00-2f1331fd4a98"
"SampleId": "4666fcd0-0179-44d8-a66d-bc65a4acd968",
"SampleCode": "88829384",
"LocationId": "ee410fb0-6a99-4b0e-a95e-83abe6aa5a5e",
"LocationName": "CF Center",
"SubLocationId": "00000000-0000-0000-0000-000000000000",
"SubLocationName": "",
"Products": [{
"ProductId": "ef0533fc-a11c-43a2-ab84-1c9bdabc9e7f",
"ProductCode": "ADROAN-830"
}
],
"PayloadId": "5385f2a9-88da-4c38-88a5-5f6f875ecb74",
"EventGroupName":"sample",
"EventDatetimeUtc": 1601969236991,
"Action": "Checked-in",
"Actor": {
"UserId": "205c0bbd-ea29-4250-87f5-bdb254a91075"
}
}
User Webhooks
Key | Type | Description |
Action |
String | The action that was performed.
Appears as one of the following:
|
Actor |
Object | The user or the actor that triggered the event.
Note: We will send the UserID only, not the user name. |
PayloadId | String | ID of the payload (GUID) |
EventGroupName | String | The event group triggered. Value: user |
EventDatetimeUtc |
Long | Unix Timestamp (milliseconds) - the number of milliseconds since the Unix Epoch |
UserId |
String | ID of the user (GUID) Notes: We will send UserID only, no user name. |
Webhook Payload Example:
When a user logs in, the payload would appear as shown below.
{
"Action":"UserLoggedIn",
"Actor":{
"UserId":"19e62847-f302-4e32-9e4a-b6d8c304fcbe"
},
"PayloadId":"c02f0f73-1706-4973-9a4d-38007d6685e9",
"EventGroupName":"user",
"EventDatetimeUtc":1595204418000,
"UserId":"19e62847-f302-4e32-9e4a-b6d8c304fcbe",
}
Task Webhooks
Key | Type | Description |
Action |
String | The action that was performed.
Appears as one of the following:
|
Actor |
Object | The user or the actor that triggered the event.
Note: We will send the UserID only, not the user name. |
PayloadId | String | ID of the payload (GUID) |
EventGroupName | String | The event group triggered. Value: task |
EventDatetimeUtc | Long | Unix Timestamp (milliseconds) - the number of milliseconds since the Unix Epoch |
TaskId | String | ID of the task (GUID) |
Assignee | Object | The assignee of this task. Notes: We will send UserID only, no user name. |
JobId | String | ID of the job (GUID) |
JobCode | String | Job code |
ClientId | String | ID of the client (GUID). |
ClientName | String | Client name |
ProductId | String | ID of the product (GUID) |
ProductCode | String | Product code |
StepId | Int | ID of the step |
StepName | String | Step name |
StepStatusId | Int | ID of the Step Status |
StepStatusName | String | Step Status name |
ShootingTypeId | Int | ID of the Shooting type (aka Production type) |
ShootingTypeName | String | Shooting type name (aka Production type name) |
StyleGuideId | String | ID of the Style guide |
StyleGuideName | String | Style guide name |
WorkflowId | String | ID of the Workflow |
WorkflowName | String | Workflow name |
WorkUnitId | String | ID of the WorkUnit (GUID) |
Webhook Payload Example:
When a Photography task is completed, the payload would appear as shown below.
{
"Action":"completed",
"Actor":{
"UserId":"19e62847-f302-4e32-9e4a-b6d8c304fcbe"
},
"PayloadId":"c02f0f73-1706-4973-9a4d-38007d6685e9",
"EventGroupName":"task",
"EventDatetimeUtc":1595204418000,
"TaskId":"c02f0f73-4665-4aae-aac1-c2fbc65365de",
"Assignee":{
"UserId":"19e62847-f302-4e32-9e4a-b6d8c304fcbe"
},
"JobId":"d84eef10-c8a6-437a-9f95-0b58d2a3cb38",
"JobCode":"Summer 2020",
"ClientId":"2c856b2f-aded-4609-bf67-27009603b2b9",
"ClientName":"My Big client",
"ProductId":"7a782d57-b0bf-4743-8f02-2b1a1e229869",
"ProductCode":"AW2130146",
"StepId":"3",
"StepName":"Photography",
"StepStatusId":"9000",
"StepStatusName":"Done",
"ShootingTypeId":"1",
"ShootingTypeName":"On Model",
"WorkflowId":"2b5d3e1b-1706-4973-9a4d-57ab7d6685e9",
"WorkflowName":"My first workflow",
"StyleGuideId":"2376b9cb-67db-490f-a5e8-89811ed6b087",
"StyleGuideName":"Summer on the beach 2020"
}Work Unit Webhooks
Key | Type | Description |
Action |
String | The action that was performed.
Appears as one of the following:
|
Actor |
Object | The user or the actor that triggered the event.
Note:
* Sync from data source: create/delete product
|
PayloadId | String | ID of the payload (GUID) |
EventGroupName | String | The event group triggered. Value: work unit |
EventDatetimeUtc | Long | Unix Timestamp (milliseconds) - the number of milliseconds since the Unix Epoch |
ClientId | String | ID of the client (GUID). |
ClientName | String | Client name |
ProductId | String | ID of the product (GUID) |
ProductCode | String | Product code |
ProductionTypeId | Int | Production Type ID |
ProductionTypeName | String | Production Type Name |
WorkUnitStatusId | Int | Work Unit Status ID after change |
WorkUnitStatusName | String | Work Unit Status Name after change |
WorkUnitId | String | ID of the WorkUnit (GUID) |
Webhook Payload Example:
When a production's status changes, the payload would appear as shown below.
{
"Action":"StatusChanged",
"Actor":{
"UserId":"19e62847-f302-4e32-9e4a-b6d8c304fcbe"
},
"PayloadId":"c02f0f73-1706-4973-9a4d-38007d6685e9",
"EventGroupName":"work unit",
"EventDatetimeUtc":1595204418000,
"ClientId":"2c856b2f-aded-4609-bf67-27009603b2b9",
"ClientName":"My Big client",
"ProductId":"7a782d57-b0bf-4743-8f02-2b1a1e229869",
"ProductCode":"AW2130146"
"ProductionTypeId":1,
"ProductionTypeName":"OnModel"
"WorkUnitStatusId":3000
"WorkUnitStatusName":"InProgress"
}
Job Webhooks
Key | Type | Description |
Action |
String | The action that was performed.
Appears as one of the following:
|
Actor |
Object | The user or the actor that triggered the event.
Note: We will send the UserID only, not the user name. |
PayloadId | String | ID of the payload (GUID) |
EventGroupName | String | The event group triggered. Value: Job |
EventDatetimeUtc | Long | Unix Timestamp (milliseconds) - the number of milliseconds since the Unix Epoch |
JobId | String | ID of the job (GUID) |
JobName | String | Job name |
JobCode | String | Job code |
JobStatusId | Int | Job Unit Status ID |
JobStatusName | String | Job Unit Status Name |
ClientId | String | ID of the client (GUID) |
ClientName | String | Client Name |
JobDeadlineUtc |
Long | Unix Timestamp (milliseconds) - the number of milliseconds since the Unix Epoch |
Webhook Payload Example:
When a job is deleted, the payload would appear as shown below.
{
"Action":"Deleted",
"Actor":{
"UserId":"19e62847-f302-4e32-9e4a-b6d8c304fcbe"
},
"PayloadId":"c02f0f73-1706-4973-9a4d-38007d6685e9",
"EventGroupName":"Job",
"EventDatetimeUtc":1595204418000,
"JobId":"d84eef10-c8a6-437a-9f95-0b58d2a3cb38",
“JobName:”Summer 2020”,
"JobCode":"Summer 2020”,
“JobStatusId”:3000,
“JobStatusName”:”In Progress”,
"ClientId":"2c856b2f-aded-4609-bf67-27009603b2b9",
"ClientName":"My Big client",
“JobDeadlineUtc”:1595204529000,
}
Session Webhooks
Key | Type | Description |
Action |
String | The action that was performed.
Appears as one of the following:
|
Actor |
Object | The user or the actor that triggered the event.
Note: We will send the UserID only, not the user name. |
PayloadId | String | ID of the payload (GUID) |
EventGroupName | String | The event group triggered. Value: session |
EventDatetimeUtc | Long | Unix Timestamp (milliseconds) - the number of milliseconds since the Unix Epoch |
ProductId | String | ID of the product (GUID) |
ProductCode | String | Product code |
ShootingTypeId | Int | ID of the Shooting type (aka Production type) |
ShootingTypeName | String | Shooting type name (aka Production type name) |
WorkUnitId | String | ID of the WorkUnit (GUID) |
ClientId |
String | ID of the Client (Workspace) assigned to the Session, as applicable. |
SessionId | String | ID of the Session (GUID) |
SessionCode | String | Session Code |
SessionName | String | Session Name |
PlannedTimeSlot | JSON Literal | Time of Session |
PlannedTeam |
JSON Literal | PlannedTeam consists of individuals participating in that session, specifies who they are, and their respective roles. |
PlannedProducerId | String | ID of the Planned Producer (GUID) |
PlannedProducerName | String | Planned Producer Name |
PlannedSetId | String | ID of the Planned Set (GUID) |
ProductionItemId
| String | ID of the Production Item (GUID) |
PlanningStatus
|
JSON Literal | Status (ID, Name) of the Planning
Appears as one of the following:
|
ActionSource | String | The application that triggered the event. |
Webhook Payload Example:
When a session is added to, the payload would appear as shown below.
{
"ActionSource": "AddFromGateway",
"ClientId": "07a2a5cb-7525-4e7b-b032-3f647a8a068c",
"SessionId": "cba7640d-452b-417b-8bbc-aaa92a763359",
"SessionCode": "SES113736CF",
"SessionName": "Quyen Test Add session",
"PlannedTimeSlot": "[{\"PlannedStart\":\"2023-12-10T01:00:00\",\"PlannedEnd\":\"2023-12-10T10:00:00\"}]",
"PlannedTeam": "{\"Assistant\":\"phuonglth+dev@creativeforce.io,dungnt+sing@creativeforce.io\",\"Photographer\":\"hanh@creativeforce.io\",\"HairAndMakeup\":\"duongvtt+dev@creativeforce.io\",\"DigitalProcessing\":\"hanttn@creativeforce.io\"}",
"PlannedProducerId": "429f37e8-4583-4541-89d9-49230bbf046f",
"PlannedProducerName": "DuongVTT DEV",
"PlannedSetId": "53c4bc2d-244a-490e-9f75-e3554246b6dd",
"ProductionItemId": "1e6b01e0-671e-462c-b266-692d6a16eb92",
"PlanningStatus": "{\"PlanningStatusId\":2,\"PlanningStatus\":\"Unplanned\"}",
"WorkUnitId": "1e6b01e0-671e-462c-b266-692d6a16eb92",
"ShootingTypeId": 1,
"ProductId": "99ecc381-41f1-4782-bcd4-e762e50a8a93",
"ProductCode": "Product_230831_5",
"PayloadId": "d483e7cb-e03b-4ccd-b307-22ca3e990e96",
"EventGroupName": "session",
"EventDatetimeUtc": 1701935735820,
"Action": "AddedToSession",
"Actor": {
"UserId": "600a5a14-8bd5-4d48-888e-f2b0bf2defc8"
}
}
Validating Webhooks
Step 1: Hash JSON Payload
Validate webhooks in your application by hashing the JSON payload of the webhook using the SHA256 algorithm. This will require the secret key generated when you first set up the webhook in Gamma.
Step 2: Compare to Webhook Signature
Then, you can compare the hash with the webhook signature. If they match, the webhook can be trusted.
📝 Notes:
The X-CF-Signature is 32 bytes and in hexa format. It's therefore important to make sure your hash function is returning 32-byte hexa as well.
Using a plain == operator is not advised. A method like secure_compare performs a "constant time" string comparison, which renders it safe from certain timing attacks against regular equality operators.
Example:
Below is an example of this process. The example is written in Ruby for a Sinatra server, but it should give you a good idea of the approach.
require 'sinatra'
require 'json'
post '/payload' do
request.body.rewind
payload_body = request.body.read
verify_signature(payload_body)
push = JSON.parse(payload_body)
puts "I got callback, JSON: #{push}"
return halt 200, "OK"
end
def verify_signature(payload_body)
signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('SHA256'), ENV['SECRET_TOKEN'], payload_body)
return halt 500, "Signatures didn't match!" unless Rack::Utils.secure_compare(signature, request.env['HTTP_X_CF_SIGNATURE'])
end
Security
Below are a few various callouts for security-related items to be aware of.
Security Feature | Notes |
Content Type | Creative Force always uses application/json as the content type. |
Enable/Disable | You can choose to disable a webhook if you want to pause it temporarily.
|
Retry | The CF system will POST data to the payload URL specified and expects a 2xx HTTP status code return.
If no 2xx HTTP status is returned, the system will automatically retry up to 3 times. Each retry triggers after 5 to 10 minutes. |



