form_submission_created
relationships.object.data identifies the form submission, and included contains the form submission, its files (as upload resources) and the loyalty member (person) it was matched to. Unlike in the REST API, the post, experience/form and person relationships always carry their linkage.
The payload below is the default JSON:API document. The webhook's payload_include, payload_fields and payload_transformation_template change it, and its filter decides whether the event is sent at all.
Request
We send the event to your webhook's url with POST (or the webhook's http_method) and a JSON:API document as the body (Content-Type: application/vnd.api+json). See Receiving Webhooks for retries and Webhook Event Signatures to verify the request.
Headers
| Header | Description |
|---|---|
X-TINT-Signature | timestamp,signature, where timestamp is the Unix time (in seconds) at which the delivery was signed and signature is the Base64-encoded HMAC-SHA256 of timestamp.body, keyed with the webhook's signing secret. Only sent when the webhook has a signing secret. Every attempt is signed again, so retries carry a new timestamp. See Webhook Event Signatures. |
API-Version | The API version the payload was serialized with, unless the webhook overrides this header. |
User-Agent | Always TINT Webhook/2.0, unless the webhook overrides this header. |
Payload
| Field | Type | Description |
|---|---|---|
data | object | |
data.id | string | The event ID. It stays the same on every retry of this event, so use it to detect duplicate deliveries. It is also the ID to use with Retrieve a webhook event. |
data.type | string | Always event. One of event. |
data.attributes | object | |
data.attributes.name | string | The event name. One of form_submission_created. |
data.attributes.created_at | string (date-time) | When the event happened (the event was recorded), not when this delivery was sent. Retries keep the original value. |
data.relationships | object | |
data.relationships.object | object | The form submission this event is about. |
data.relationships.object.data | object | |
data.relationships.object.data.type | string | One of form_submission. |
data.relationships.object.data.id | string | |
data.relationships.team | object | The team the webhook belongs to. |
data.relationships.team.data | object | |
data.relationships.team.data.type | string | One of team. |
data.relationships.team.data.id | string |
Included resources
The sideloaded resources: the form submission, its files (as upload resources) and the loyalty member (person) it was matched to. Unlike in the REST API, the post, experience/form and person relationships always carry their linkage.
form_submission
| Field | Type | Description |
|---|---|---|
id | string | The ID of the form submission. |
type | string | One of form_submission. |
attributes | object | |
attributes.data (optional) | object | The submitted fields as key/value pairs, exactly as they were sent. For poll experiences, post_id is the ID of the post voted for and vote the number of votes. |
attributes.user_agent (optional) | string or null | The User-Agent header of the request that created the submission. |
attributes.referer (optional) | string or null | The Referer header of the request that created the submission. |
attributes.locale (optional) | string or null | The locale query parameter sent with the submission, if any. |
attributes.metadata (optional) | object or null | Extra data sent in the X-TINT-Metadata header (as a URL-encoded query string) when the submission was created. null when none was sent. |
attributes.remote_ip_city (optional) | string or null | The city of the submitter, based on their IP address. null when unknown. |
attributes.remote_ip_most_specific_subdivision (optional) | string or null | The ISO 3166-2 code (without the country prefix) of the most specific subdivision of the submitter, based on their IP address. null when unknown. |
attributes.remote_ip_country (optional) | string or null | The ISO 3166-1 alpha-2 code of the submitter's country, based on their IP address. null when unknown. |
attributes.city (optional) | string or null | Deprecated; use remote_ip_city, remote_ip_most_specific_subdivision, and remote_ip_country instead. The comma-separated combination of those three attributes, or null when all are unknown. |
attributes.created_at (optional) | string (date-time) | The date and time the form was submitted. |
relationships (optional) | object | Unless stated otherwise, a relationship's data is only present when it's listed in the include query parameter; otherwise its meta.included is false. |
relationships.person (optional) | object | The loyalty member who submitted the form. data is always present; its id is null when the submitter wasn't matched to a loyalty member. |
relationships.person.data (optional) | object | |
relationships.experience (optional) | object | The experience the form was submitted to. Only present when the request is authenticated with the experiences:read scope. |
relationships.experience.data (optional) | object | |
relationships.experience.meta (optional) | object | |
relationships.post (optional) | object | The post voted for in a poll experience (from data.post_id). Only present when the request is authenticated with the posts:read scope. When included, data is null if the submission has no matching post. |
relationships.post.data (optional) | object or null | |
relationships.post.meta (optional) | object | |
relationships.terms (optional) | object | The rights terms the submitter accepted. Only returned for submissions to forms (not experiences), and never included. |
relationships.terms.meta (optional) | object | |
relationships.files (optional) | object | The files attached to the submission. |
relationships.files.data (optional) | array of object | |
relationships.files.meta (optional) | object |
person
A member of the team's loyalty program.
| Field | Type | Description |
|---|---|---|
id | string | The ID of the loyalty member. |
type | string | One of person. |
attributes | object | |
attributes.first_name (optional) | string or null | The member's first name. |
attributes.last_name (optional) | string or null | The member's last name. |
attributes.email (optional) | string or null (email) | The member's email address. |
attributes.external_id (optional) | string or null | The member's ID in your own system, if one was provided when they enrolled. |
attributes.created_at (optional) | string or null (date-time) | When the member was created. |
attributes.updated_at (optional) | string or null (date-time) | When the member was last updated. |
attributes.enrolled_at (optional) | string or null (date-time) | When the member enrolled in the loyalty program. |
attributes.last_interaction_at (optional) | string or null (date-time) | When the member last interacted with the loyalty program. |
upload
A file attached to a form submission, sideloaded when files is included (always in webhook payloads).
| Field | Type | Description |
|---|---|---|
id | string | The signed ID of the file. |
type | string | One of upload. |
attributes | object | |
attributes.content_type (optional) | string | The file's media type. |
attributes.url (optional) | string (uri) | The URL of the file. The URL is signed and expires after a short time. |
attributes.thumbnail_url (optional) | string or null (uri) | The URL of a thumbnail of the file, or null for files that can't be previewed (such as PDFs). |
relationships (optional) | object | |
relationships.asset (optional) | object | The asset created from the file, if any. Only returned in webhook payloads or when the access token has the assets:read scope. |
relationships.asset.data (optional) | object or null |
Example
{
"data": {
"id": "1377457",
"type": "event",
"attributes": {
"created_at": "2025-03-14T16:42:07.000Z",
"name": "form_submission_created"
},
"relationships": {
"object": {
"data": {
"type": "form_submission",
"id": "640021"
}
},
"team": {
"data": {
"type": "team",
"id": "1"
}
}
}
},
"included": [
{
"id": "640021",
"type": "form_submission",
"attributes": {
"data": {
"first_name": "Jane",
"last_name": "Doe",
"email": "jane.doe@example.com",
"opt_in": "yes"
},
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Safari/605.1.15",
"remote_ip_city": "Vancouver",
"remote_ip_most_specific_subdivision": "BC",
"remote_ip_country": "Canada",
"referer": "https://www.example.com/contest",
"locale": "en",
"created_at": "2025-03-14T16:42:07.000Z",
"metadata": {},
"city": "Vancouver, BC, Canada"
},
"relationships": {
"post": {
"data": null
},
"person": {
"data": {
"type": "person",
"id": "65f3a1c2e4b0a91d2c7e8f10"
}
},
"experience": {
"data": {
"type": "experience",
"id": "2012"
}
},
"files": {
"data": [
{
"type": "upload",
"id": "eyJfcmFpbHMiOnsiZGF0YSI6OTg3NjU0LCJwdXIiOiJibG9iX2lkIn19--3b5f0c1d2e"
}
]
},
"terms": {
"meta": {
"included": false
}
}
}
},
{
"id": "eyJfcmFpbHMiOnsiZGF0YSI6OTg3NjU0LCJwdXIiOiJibG9iX2lkIn19--3b5f0c1d2e",
"type": "upload",
"attributes": {
"content_type": "image/jpeg",
"url": "https://cdn.example.com/uploads/entry-photo.jpg",
"thumbnail_url": "https://cdn.example.com/uploads/entry-photo-thumbnail.jpg"
}
},
{
"id": "65f3a1c2e4b0a91d2c7e8f10",
"type": "person",
"attributes": {
"first_name": "Jane",
"last_name": "Doe",
"email": "jane.doe@example.com",
"external_id": "CUS-004211",
"created_at": "2025-03-14T16:42:07.000Z",
"updated_at": "2025-03-14T16:42:07.000Z",
"enrolled_at": "2025-03-14T16:42:07.000Z",
"last_interaction_at": "2025-03-14T16:42:07.000Z"
}
}
]
}Responses
| Status | Description |
|---|---|
404 | 404 Not Found or 410 Gone stops delivery of this event immediately: it is marked failure and not retried. The attempt still counts toward the webhook's failure limit. |
410 | 404 Not Found or 410 Gone stops delivery of this event immediately: it is marked failure and not retried. The attempt still counts toward the webhook's failure limit. |
2XX | Any status below 400 (typically 2XX) acknowledges the event: we mark it success and don't send it again. The response body is ignored (we store it with the attempt, for your reference). Redirects are not followed, and a 3XX response also counts as acknowledged, so point the webhook at the final URL. |
| Other | Any other 4XX or 5XX status, a timeout or a connection error fails the attempt, and we retry up to 7 more times with an increasing delay (roughly 1 minute, 1 minute, 2 minutes, 13 minutes, 1 hour, 4 hours, then 13 hours later), after which the event is marked failure. More than 50 failed attempts for the same webhook within a week, without a successful delivery in between, set the webhook to inactive. |