Learn how to integrate Craft.io’s Pass-through Identity API with your existing applications, forms, or systems to submit feedback while preserving the original submitter’s identity.
This guide covers authentication using signed JSON Web Tokens (JWTs) or an account API key, identifying submitters, submitting feedback, and populating custom fields.
For instructions on enabling Pass-through Identity and choosing between the available feedback collection approaches, see Collect Feedback Directly from Your Product and Existing Systems into Craft.io.
Before you begin
Before implementing Pass-through Identity, make sure you have:
Pass-through Identity enabled: An admin must generate an authentication secret key under Feedback Portal Settings → Feedback Widgets → Authentication.
Authentication credentials: Either the generated secret key for JWT authentication or an account API key, which you can request from your Craft.io Customer Success Manager.
A way to identify submitters: Your application or system should provide user information, such as a unique ID, name, or email address.
💡 Note: Your authentication method depends on whether you’re submitting feedback from your server or a browser. The next section explains the available options.
1. Authenticate your requests
Pass-through Identity supports two authentication methods: signed JWTs and account API keys. Choose the method that best fits your integration.
Route | Credential | Use it when |
Signed token, from your server | A token you sign with the portal's secret key, sent as | You want to stay self-serve. The secret key is already yours; there's nothing else to request. |
Signed token, from a browser | The same token, fetched by the page from an endpoint on your server | The call is made from a page. An API key in a page can be read by anyone who opens it; a short-lived token can't be reused. |
Account API key |
| Your server makes the call and you also want to read the portal's categories, forms, or companies. See the limit below. |
💡 What a signed token can't do: it submits feedback and uploads attachments, and that's all. Reading the portal's reference data — /categories, /importances, /forms, /custom_fields, and /companies — is refused with a 403 on a token. If your integration needs to read those, use an account API key, or look the values up once and keep them in your configuration.
To get an account API key, contact your Customer Success Manager (CSM).
Signing your own token
If you're using the secret key route, the token is an ordinary JWT, signed HS256 with the portal's secret key. It must include:
Claim | Required | Description |
| Yes | Your own ID for this person, as a string. This is the primary identity key. |
| Yes | Their email address. |
| Yes | Token creation timestamp, in Unix seconds. |
| Yes | Token expiration timestamp, in Unix seconds. Must be no more than one hour after |
| No | Their name as your system holds it. |
| No | The company the person belongs to, written exactly as it appears in your portal's company list. |
Identity then comes from the token's claims only: sending submitterId, owner, submitterName, issuer, or anonymous in the request body alongside a token is refused.
💡 Need working code? The Feedback Widget Developer Guide has a complete Node.js example of signing this same kind of token. Nothing about the signing process is widget-specific — the claims above are everything Craft.io requires.
2. Identify the submitter
When using an account API key, include the submitter’s identity in the JSON request body, alongside the feedback title and description.
When using a signed JWT, the submitter’s identity is taken from the token’s claims instead. In this case, identity fields must be omitted from the request body.
The following example shows how to identify a submitter when using an account API key.
{
"title": "Bulk import fails over 10k rows",
"description": "Stops at about 10,000 and reports no error.",
"submitterId": "crm-5541",
"submitterName": "Dana Whitfield",
"owner": "dana@northwind.test",
"issuer": "northwind-crm"
}Field | What to send |
| Your own ID for this person. This is the primary identity key: send it and Craft.io matches on it rather than on the email address. |
| Their email address. Used for status-update emails, and as the identity key when you send no |
| Their name as your system holds it. Omit it and the portal can only show the local part of the email where a person's name should appear. |
| Which of your systems the ID came from. Worth sending if more than one system posts to the same portal, so two different people with the same ID don't collide. |
| The company the person belongs to, written exactly as it appears in your portal's company list. A name matching nothing is refused rather than created. Accepted on restricted portals; a public portal files everything under its default company. |
| Send |
💡 Use a consistent submitter ID. Craft.io uses submitterId to recognize the same person across submissions, even if their name or email address changes. This helps prevent duplicate submitter records.
3. Submit the feedback
Post to your portal's submission endpoint:
POST https://{api-host}/feedback_portal/{portalId}/plain_feedback
Use /plain_feedback for standard feedback submissions, or /feedback when submitting custom field values. See Section 4 for details.
Which host
Your account is hosted in | Use |
US |
|
EU |
|
If you're not sure which you're on, check the address you use for Craft.io itself, or ask your CSM. The examples below use the US host.
Values you'll need
Value | Where to get it |
| Portal settings → Feedback Widgets → Installation, where it appears as |
| Optional; leave it out and the feedback lands in the portal's default category. To choose one, the IDs come from |
| Optional, and it takes the name: send |
| The company name, exactly as your portal lists it. No id needed. |
💡 Start with the essentials. The feedback title is the only required content field. Category, importance, and company are optional and use the portal’s defaults when omitted. To attribute feedback to a specific person, include the appropriate identity information for your chosen authentication method.
Follow these steps:
Build the request body with your title, description, and the identity fields from the previous section.
Send it to the endpoint above with your chosen credential.
curl --request POST \
"https://api.craft.io/feedback_portal/$PORTAL_ID/plain_feedback" \
-H "x-api-key: $CRAFT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Bulk import fails over 10k rows",
"description": "Stops at about 10,000 and reports no error.",
"categoryId": "2305843010139563682",
"submitterId": "crm-5541",
"submitterName": "Dana Whitfield",
"owner": "dana@northwind.test",
"issuer": "northwind-crm"
}'
With a signed token, drop the identity fields from the body and send the token instead:
curl --request POST \
"https://api.craft.io/feedback_portal/$PORTAL_ID/plain_feedback" \
-H "Authorization: Bearer $SIGNED_JWT" \
-H "Content-Type: application/json" \
-d '{
"title": "Bulk import fails over 10k rows",
"description": "Stops at about 10,000 and reports no error.",
"categoryId": "2305843010139563682"
}'
A successful response returns the new item's id, its shortId, and a link to it in the portal.
💡 Using custom fields? To submit additional information such as product area, browser version, or severity, follow the instructions in Section 4.
4. Fill in your form's custom fields
Each feedback category in your portal is associated with a form, which may include custom fields in addition to the standard title and description.
To submit values for these fields, use the /feedback endpoint instead of /plain_feedback and include a customFields array in your request body.
POST https://{api-host}/feedback_portal/{portalId}/feedback💡 Note: /plain_feedback refuses custom fields. Sending customFields there comes back as a 422; they aren't silently dropped.
Find out which fields to send
Ask the portal for its forms, with the choices for each select field included:
curl "https://api.craft.io/feedback_portal/$PORTAL_ID/forms?includeOptions=true" \
-H "x-api-key: $CRAFT_API_KEY"
Each form lists the categories that use it and its fields, in the order the form shows them:
{
"metadata": { "portalId": "2305843010139563650", "count": 2 },
"forms": [
{
"id": "2305843010139563676",
"name": "Bug report",
"isDefault": false,
"categories": [ { "id": "2305843010139563682", "name": "Bugs" } ],
"fields": [
{
"id": "2305843010139571301",
"name": "Product area",
"type": "SELECT",
"multi": false,
"required": true,
"options": [
{ "id": "2305843010139571310", "label": "Registration" },
{ "id": "2305843010139571311", "label": "Mobile app" }
],
"source": "portal",
"orderIndex": 1
},
{
"id": "2305843010139571302",
"name": "App version",
"type": "TEXT",
"required": false,
"source": "portal",
"orderIndex": 2
}
]
},
{ "id": "...", "name": "Default Form", "isDefault": true, "categories": [], "fields": [] }
]
}
To pick the form for your feedback:
Find the form whose
categoriesinclude the category you're submitting to.If no form lists it, or you send no
categoryIdand the default category has no form of its own, use the form with"isDefault": true. That's the form Craft.io checks your fields against.
💡 Read this once, not on every submission. Field and option IDs remain the same even when renamed. Retrieve them when setting up your integration, store them in your configuration, and refresh them when fields or options are added or removed.
💡 An account API key is required to retrieve form details. Signed JWTs can submit feedback with custom fields but cannot retrieve form definitions through /forms (403 Forbidden).
If you’re using JWT authentication, retrieve the form and field IDs once using an account API key, then store them in your integration’s configuration.
Write each value
customFields is a list. Each entry names one field by its id from the form, and carries either value (one value) or values (several):
Field type | What to send | Example |
| A string, up to 65,535 characters |
|
| A number. The field's minimum and maximum, if set, are enforced. |
|
| A date as |
|
| A full |
|
| One option, by its |
|
| One or more options, by |
|
| The email address of someone on your Craft.io team |
|
| Nothing. It's calculated, and any value is refused. | — |
💡 Note: An option's id is the safer choice: it survives a PM renaming the option, and a label that matches nothing is refused rather than created.
The full request
curl --request POST \
"https://api.craft.io/feedback_portal/$PORTAL_ID/feedback" \
-H "x-api-key: $CRAFT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Registration page times out for large events",
"description": "Over 2,000 attendees and the page never finishes loading.",
"categoryId": "2305843010139563682",
"submitterId": "crm-5541",
"submitterName": "Dana Whitfield",
"owner": "dana@northwind.test",
"issuer": "northwind-crm",
"sourceUrl": "https://app.northwind.test/acme/events/812/registration",
"customFields": [
{ "id": "2305843010139571301", "value": "2305843010139571310" },
{ "id": "2305843010139571302", "value": "4.18.2" }
]
}'
Here, the first customFields entry picks Registration for Product area by its option id, and the second fills in App version. sourceUrl is optional: it records the page the person was on, and it appears on the item in Craft.io.
With a signed token, the body is the same, minus the identity fields.
Rules the API checks
Every field must be on the form you picked above. A field from another category's form, or an id the portal doesn't have, is a
422.Only fields with
"source": "portal"can be set when you create feedback. These are the fields on your portal's forms.Send each field once. The same id twice in one request is refused.
Send a value for every field the form marks
"required": true. Always provide a value for every required field — a required field sent with an empty value is refused.A single-choice field takes one option. Sending several values to a field with
"multi": falseis refused.
How submitters are represented in Craft.io
When feedback is submitted on behalf of a person for the first time, Craft.io automatically creates a portal user for them.
These users don’t need a password or a Craft.io account. Their identity is maintained so your team can associate feedback with the correct person and send relevant status updates.
If email notifications are enabled, submitters can receive updates at the email address you provided and unsubscribe at any time.
Security considerations
Keep your secret key on the server. Never expose it in frontend JavaScript, HTML, or any publicly accessible configuration — anyone with it can sign tokens as any of your users.
Use short-lived tokens. The maximum supported validity period is one hour; a shorter expiration, such as 15 minutes, limits how long a leaked token stays useful.
Don't cache tokens. Generate a fresh one per session and avoid storing them in logs or client-side storage.
Authenticate your own token-issuing endpoint. If you expose an endpoint that signs tokens for a browser page, make sure only your own authenticated users can call it.
Rotate or revoke the secret key if it's compromised. Rotating under Feedback Portal Settings → Feedback Widgets → Authentication keeps the previous key valid for seven days; revoking invalidates it immediately. Either action affects every integration using that key, including the Feedback Widget if you also use it.
Treat the account API key with the same care as the secret key. It can read portal data as well as submit feedback, so restrict it to your server-side code.
Troubleshooting
What you get | What it usually is |
| Pass-through Identity isn't enabled on the portal. An admin needs to generate a secret key; see Collect Feedback Directly from Your Product and Existing Systems into Craft.io. |
| Sent to |
| You're using a signed token. Reading forms needs an account API key. |
| The name matches no company in your portal, or the portal is public. Check the portal's company list. |
| The API key can't write feedback on this portal. Check its scopes and portal access with your CSM. |
| You sent identity in the body alongside a signed token. With a token, identity comes from the token only. |
Feedback shows as Anonymous | No |
One person became two | Matching fell back to the email address and it changed. Send |
If the issue persists, contact Craft.io Support.
What comes next
Collect Feedback Directly from Your Product and Existing Systems into Craft.io - Learn how to enable Pass-through Identity and configure your Feedback Portal.
Integrating the Feedback Widget into Your Application - Developer Guide - Learn how to embed a feedback form in your application using the same authentication mechanism.
Craft.io public APIs for workspace, portfolio, and feedback portal - the wider API reference, including how to request an account API key.
Need more guidance? 🙋 Our LIVE support team (at the bottom right corner of your screen) replies to ANY question!
