Responses are generated using AI and may contain mistakes.
IMPORTANT: We're updating product naming across our documentation. Engage is now Customer Engagement and Elevate is now Product Discovery. This doesn't change Voyado's services, functionality, or terms of your agreement. Be aware, as these changes happen, that you may see both the old and new names being used.
When developing a client application that communicates with Customer Engagement using your tenant-specific API key, it’s essential to follow certain non-functional requirements (NFRs).The guidelines listed here ensure that your integration is secure and resilient and aligns with Voyado’s best practices.
Use HTTPS with TLS 1.2 or higher for encryption in transit
All API requests must be made over HTTPS to protect data, including the API key, in transit. TLS ensures that credentials and payloads cannot be intercepted or tampered with during transmission.
Consider restricting API access for your tenant to specific IP addresses using Voyado’s IP filtering feature. This adds an extra layer of security by ensuring that only requests from authorized IPs are accepted.
Your client should be able to handle temporary issues gracefully without overwhelming the API or duplicating requests unnecessarily.Retries should be performed only under the following conditions:
When server timeout (504) is received:This happens when processing the request taked too long. The timeout threshold is different for each endpoint, and is generally shorter for customer facing experiences.
In the case of other 5xx server errors: These indicate temporary issues on the Voyado side. It is safe to retry with exponential back-off.
Connection timeouts or network interruptions: These are transient connectivity issues can occur between your client and Voyado’s API.
There is a 408 (Request Timeout): The server didn’t get the full request in time, often due to network delays. it is safe to retry with back-off, especially for idempotent requests.
In the case of 429 (Too Many Requests): The client is being rate-limited. Retry after a short delay using exponential back-off.
Do not retry under these conditions:
There is a 4xx client error that is not 408 or 429: Errors such 400 (Bad Request), 401 (Unauthorized), 403 (Forbidden), or 404 (Not Found) indicate a problem with the request itself and will not succeed on retry without changes being made.
There are invalid payloads or schema violations: Fix the validation issues before retrying the same request.
There are duplicate-sensitive POST operations: Instead of blindly retrying, confirm whether the initial request succeeded (such as by using a GET) to avoid duplicating actions.
If your application stores data retrieved from the API, implement appropriate security measures to protect this data, especially if it includes personal or sensitive information.
If your system subscribes to webhooks from Voyado Customer Engagement, your webhook listener must be designed for security, reliability, and scalability.
Each webhook request includes an HMAC-SHA256 signature in the x-voyado-signature header, based on a shared secret. Your listener should compute the expected signature and compare it to the one received to verify that the payload genuinely comes from Voyado. This protects against spoofed or unauthorized requests.
Use HTTPS and restrict access to the webhook endpoint
Only expose your listener over HTTPS to ensure data is encrypted in transit. Restrict access to your webhook endpoint by allowing only traffic from Svix’s external delivery IPs — the service Voyado uses to send webhooks. While not required, this is strongly recommended to reduce the risk of unauthorized access.
If sensitive data is to be included in some webhook payload, you can configure your webhooks to use AES-256 encryption. Your system must then decrypt the payload on receipt using your private key.
Note that encryption is either enabled for all webhooks or disabled entirely.
Webhook endpoints should return a 200 OK response as soon as possible to acknowledge receipt. Any heavy processing (e.g., database updates or downstream API calls) should be handled asynchronously. This ensures reliable delivery and prevents timeouts.
If multiple events are sent in a short time (e.g., during peak campaigns), your listener should throttle incoming traffic and queue processing to avoid failures or overload.
Log incoming webhook events and responses, including timestamps, event types, and delivery status. Monitor for spikes, failures, or validation errors to ensure system health.
{"docsConfig":{"theme":"mint","$schema":"https://mintlify.com/docs.json","name":"Voyado Docs","colors":{"primary":"#fe4a49","light":"#ffffff","dark":"#fe4a49"},"logo":{"light":"https://mintcdn.com/voyado/fiZZVymeDExCOKTK/logo/light.svg?fit=max\u0026auto=format\u0026n=fiZZVymeDExCOKTK\u0026q=85\u0026s=114e763881bcc646add31724b257f726","dark":"https://mintcdn.com/voyado/fiZZVymeDExCOKTK/logo/dark.svg?fit=max\u0026auto=format\u0026n=fiZZVymeDExCOKTK\u0026q=85\u0026s=68eaa3b1f64893ebcee4c7d65f20ecbc","href":"/docs"},"favicon":"https://mintcdn.com/voyado/_EinvHkwtU2pq43w/favicon.png?fit=max\u0026auto=format\u0026n=_EinvHkwtU2pq43w\u0026q=85\u0026s=4109ede7a6d04311c67ffa2f695ce527","appearance":{"default":"light"},"background":{"decoration":"grid"},"navbar":{"links":[{"icon":"/icons/help-center-link.png","href":"https://help.engage.voyado.com/hc/en-gb","label":"Help Center"}],"primary":{"type":"button","label":"Contact us","href":"https://voyado.com/about-voyado/contact-us/"}},"footer":{"socials":{"facebook":"https://www.facebook.com/voyado","linkedin":"https://www.linkedin.com/company/voyadoab"}},"fonts":{"heading":{"family":"Voyado Text","source":"https://mintlify-assets.b-cdn.net/VoyadoText-Medium.woff2","format":"woff2"},"body":{"family":"Voyado Regular","source":"https://mintlify-assets.b-cdn.net/VoyadoText-Regular.woff2","format":"woff2"}},"integrations":{"cookies":{"key":"mintlify_telemetry","value":"granted"}},"banner":{"content":"IMPORTANT: We're updating product naming across our documentation. **Engage** is now **Customer Engagement** and **Elevate** is now **Product Discovery**. This doesn't change Voyado's services, functionality, or terms of your agreement. Be aware, as these changes happen, that you may see both the old and new names being used.","dismissible":false},"errors":{"404":{"redirect":false,"title":"The page you are looking for can't be found. Search for it in the box above.","description":"Or press [here](https://developer.voyado.com/) to come to the start page and the menu."}},"contextual":{"options":["claude","chatgpt","assistant","copy","view","mcp"],"display":"header"},"styling":{"latex":false},"navigation":{"pages":[]}},"deployment":{"deploymentMetadataValue":{"subdomain":"voyado","hasAudioNarration":false,"actualSubdomain":"voyado","customDomains":["developer.voyado.com"],"gitSource":{"type":"github","owner":"voyado","repo":"developer-docs","deployBranch":"main","contentDirectory":"","isPrivate":true,"mountPath":"","label":""},"chroma":{"liveDeploymentHistoryId":"6ae885c4b4392014d523de34","collectionId":"3f9c1959-fc22-41aa-862e-3e1371dcf5c2","schemaVersion":"v3","latestBuiltDeploymentHistoryId":"6ae885c4b4392014d523de34"},"feedback":{"thumbs":true,"edits":false,"issues":false},"entitlements":{"AI_CHAT":{"status":"ENABLED"},"REMOVE_BRANDING":{"status":"DISABLED"},"WEB_MCP":{"status":"ENABLED"},"NARRATION":{"status":"ENABLED"}},"buildId":"6ae885c4b4392014d523de34","clientVersion":"0.0.4045","searchFilterCounts":[{"language":"default","tag":"Integration","count":51},{"language":"default","tag":"Data access","count":26},{"language":"default","tag":"E-com and communication","count":19},{"language":"default","tag":"File based transfer","count":18},{"language":"default","tag":"Contacts","count":15},{"language":"default","tag":"BI export","count":14},{"language":"default","tag":"BI Exports available","count":13},{"language":"default","tag":"E-com and tracking","count":11},{"language":"default","tag":"Delta share","count":9},{"language":"default","tag":"Home","count":8}]},"assistantConfig":{"enableStarterQuestions":false},"basePath":"","hasChatPermissions":true,"hasWebMcpPermissions":true,"toggles":[{"name":"kill-isr","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-mintlify-slack","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-integrations-settings","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-pr-grouping","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"custom-prefix","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"analytics-v7","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"user-flows-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"no-publish-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-onboarding","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"llms-txt-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving-live","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"stripTemperatureMiddleware","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"support-platform-pylon-zendesk","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"instant-rollback","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications-server","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-fable","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fixed-ai-action-pricing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automation-resolve-merge-conflict","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"scoped-publishing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"mintql","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"page-level-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-version-history-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-page-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"full-page-search","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workspace-projects","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"assistant-v5","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"byok-sep-25","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fork-pr-previews","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"revalidation-jwt","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"vite-origin","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-updated-9-15","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-sso-scim","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"hobby-abuse-scan","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"dashboard-inbox","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-signup","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"turbopuffer-dual-write","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-enabled","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"approve-review-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"sso-setup-deadline-banner","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false}],"privacy":{"consentBanner":{"enabled":false}},"googleFontsInlined":false},"customCss":[{"filePath":"styles/style.css","content":".border-b{border-bottom-width:2px;border-color:rgb(var(--gray-200))}.prose-sm{font-size:.9rem;line-height:1.7}.prose :where(code):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-code);font-weight:500;background-color:#f8f8f7;border:1px solid #c7dbe8;border-radius:4px}.py-1\\.5{padding-top:1px;padding-bottom:1px}@media (min-width:1024px){.lg\\:mt-8{margin-top:1rem}}.sidebar-title{margin-bottom:-.3rem;font-size:15px}#pagination{display:none!important}.prose table{border-collapse:collapse;width:100%}.prose table td,.prose table th{border:1px solid rgba(0,0,0,.15);padding:.6rem .75rem}html.dark .prose table td,html.dark .prose table th{border:1px solid rgba(255,255,255,.15)}.prose table thead tr{background-color:rgba(59,130,246,.12)}html.dark .prose table thead tr{background-color:rgba(59,130,246,.25)}.prose table thead th{font-weight:600}.prose table tbody tr:nth-child(odd) td{background-color:rgba(0,0,0,.03)}html.dark .prose table tbody tr:nth-child(odd) td{background-color:rgba(255,255,255,.05)}.prose :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.6em;margin-bottom:.7em}.prose :where(h3):not(:where([class~=not-prose],[class~=not-prose] *)),.prose :where(h4):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.5em;margin-bottom:.6em}@media (min-width:1024px){.update h2{margin-top:-2px!important}}.update-container{padding-top:15px!important;padding-bottom:25px!important;gap:5px!important}"}],"page":{"pagePath":"docs/api/non-functional-requirements","slug":"/docs/api/non-functional-requirements","pageMetadata":{"title":"Non-functional requirements","description":null,"href":"/docs/api/non-functional-requirements"},"pageType":"default","mdx":{"mdxSource":{"compiledSource":"\"use strict\";const{Fragment:_Fragment,jsx:_jsx,jsxs:_jsxs}=arguments[0];const{useMDXComponents:_provideComponents}=arguments[0];function _createMdxContent(props){const _components={a:`a`,li:`li`,p:`p`,strong:`strong`,ul:`ul`,..._provideComponents(),...props.components},{Heading,Warning}=_components;if(!Heading)_missingMdxReference(`Heading`,true);if(!Warning)_missingMdxReference(`Warning`,true);return _jsxs(_Fragment,{children:[_jsx(_components.p,{children:`When developing a client application that communicates with Customer Engagement using your tenant-specific API key, it’s essential to follow certain non-functional requirements (NFRs).`}),`\n`,_jsx(_components.p,{children:`The guidelines listed here ensure that your integration is secure and resilient and aligns with Voyado’s best practices.`}),`\n`,_jsx(Heading,{level:`2`,id:`security-best-practices`,children:`Security best practices`}),`\n`,_jsx(Heading,{level:`3`,id:`use-https-with-tls-1-2-or-higher-for-encryption-in-transit`,children:`Use HTTPS with TLS 1.2 or higher for encryption in transit`}),`\n`,_jsx(_components.p,{children:`All API requests must be made over HTTPS to protect data, including the API key, in transit. TLS ensures that credentials and payloads cannot be intercepted or tampered with during transmission.`}),`\n`,_jsx(Heading,{level:`3`,id:`store-the-api-key-securely-at-rest`,children:`Store the API key securely at rest`}),`\n`,_jsx(_components.p,{children:`If your client needs to persist the API key:`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[`Store it in a `,_jsx(_components.strong,{children:`secure secrets management system`}),` (e.g., AWS Secrets Manager, Azure Key Vault).`]}),`\n`,_jsx(_components.li,{children:`Avoid hardcoding the key in source code, config files, or environment variables exposed to user-facing environments.`}),`\n`,_jsxs(_components.li,{children:[`Use `,_jsx(_components.strong,{children:`file system-level encryption`}),` and proper access controls if stored locally.`]}),`\n`,_jsx(_components.li,{children:`Never store the API key in frontend or browser-accessible code — all calls to the Voyado API must be made from trusted backend systems.`}),`\n`]}),`\n`,_jsx(Heading,{level:`3`,id:`prevent-exposure-in-logs-or-error-messages`,children:`Prevent exposure in logs or error messages`}),`\n`,_jsx(_components.p,{children:`Do not log the API key or include it in exception traces. Redact sensitive headers before logging outbound requests.`}),`\n`,_jsx(Heading,{level:`3`,id:`use-the-correct-header-based-authentication`,children:`Use the correct header-based authentication`}),`\n`,_jsx(_components.p,{children:`Send the API key in the apikey HTTP header, not in the URL or query string, to avoid accidental exposure via browser history or intermediaries.`}),`\n`,_jsx(Heading,{level:`3`,id:`implement-ip-filtering`,children:`Implement IP Filtering`}),`\n`,_jsx(_components.p,{children:`Consider restricting API access for your tenant to specific IP addresses using Voyado’s IP filtering feature. This adds an extra layer of security by ensuring that only requests from authorized IPs are accepted.`}),`\n`,_jsx(Heading,{level:`2`,id:`testing-and-environment-readiness`,children:`Testing and environment readiness`}),`\n`,_jsx(Heading,{level:`3`,id:`use-the-staging-environment`,children:`Use the staging Environment`}),`\n`,_jsxs(_components.p,{children:[`Before deploying to production, thoroughly test your integration in `,_jsx(_components.a,{href:`/docs/introduction/staging-environment#about-the-staging-environment`,children:`Voyado’s staging environment`}),`. Doing this allows you to:`]}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsx(_components.li,{children:`Validate authentication and API connectivity.`}),`\n`,_jsx(_components.li,{children:`Test all intended API endpoints, including handling of success and error responses.`}),`\n`,_jsx(_components.li,{children:`Simulate various scenarios, such as network latency and API timeouts.`}),`\n`,_jsx(_components.li,{children:`Ensure proper logging and monitoring are in place.`}),`\n`]}),`\n`,_jsx(Heading,{level:`2`,id:`resilience-and-error-handling`,children:`Resilience and Error Handling`}),`\n`,_jsx(Heading,{level:`3`,id:`implement-appropriate-retry-logic`,children:`Implement appropriate retry logic`}),`\n`,_jsx(_components.p,{children:`Your client should be able to handle temporary issues gracefully without overwhelming the API or duplicating requests unnecessarily.`}),`\n`,_jsxs(_components.p,{children:[`Retries should be performed `,_jsx(_components.strong,{children:`only under the following conditions`}),`:`]}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[`\n`,_jsx(_components.p,{children:`When server timeout (504) is received:`}),`\n`,_jsx(_components.p,{children:`This happens when processing the request taked too long. The timeout threshold is different for each endpoint, and is generally shorter for customer facing experiences.`}),`\n`]}),`\n`,_jsxs(_components.li,{children:[`\n`,_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`In the case of other 5xx server errors:`}),` These indicate temporary issues on the Voyado side. It is safe to retry with exponential back-off.`]}),`\n`]}),`\n`,_jsxs(_components.li,{children:[`\n`,_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`Connection timeouts or network interruptions:`}),` These are transient connectivity issues can occur between your client and Voyado’s API.`]}),`\n`]}),`\n`,_jsxs(_components.li,{children:[`\n`,_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`There is a 408 (Request Timeout):`}),` The server didn’t get the full request in time, often due to network delays. it is safe to retry with back-off, especially for idempotent requests.`]}),`\n`]}),`\n`,_jsxs(_components.li,{children:[`\n`,_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`In the case of 429 (Too Many Requests):`}),` The client is being rate-limited. Retry after a short delay using exponential back-off.`]}),`\n`]}),`\n`]}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`Do not retry`}),` under these conditions:`]}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`There is a 4xx client error that is not 408 or 429:`}),` Errors such 400 (Bad Request), 401 (Unauthorized), 403 (Forbidden), or 404 (Not Found) indicate a problem with the request itself and will not succeed on retry without changes being made.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`There are invalid payloads or schema violations:`}),` Fix the validation issues before retrying the same request.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`There are duplicate-sensitive POST operations:`}),` Instead of blindly retrying, confirm whether the initial request succeeded (such as by using a GET) to avoid duplicating actions.`]}),`\n`]}),`\n`,_jsx(Heading,{level:`3`,id:`recommended-strategy`,children:`Recommended strategy`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[`Use `,_jsx(_components.strong,{children:`exponential back-off`}),` with added jitter (random delay variation).`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Cap the retries`}),` after a reasonable limit (such as 3–5 attempts).`]}),`\n`,_jsxs(_components.li,{children:[`Always `,_jsx(_components.strong,{children:`log retry attempts`}),`, including reasons and delays.`]}),`\n`]}),`\n`,_jsx(Heading,{level:`2`,id:`monitoring-and-logging`,children:`Monitoring and Logging`}),`\n`,_jsx(Heading,{level:`3`,id:`comprehensive-logging`,children:`Comprehensive Logging`}),`\n`,_jsx(_components.p,{children:`Maintain detailed logs of all API interactions, including:`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsx(_components.li,{children:`Request and response timestamps.`}),`\n`,_jsx(_components.li,{children:`Endpoint URLs and HTTP methods.`}),`\n`,_jsx(_components.li,{children:`Status codes and error messages.`}),`\n`]}),`\n`,_jsx(_components.p,{children:`This facilitates troubleshooting and performance monitoring.`}),`\n`,_jsx(Heading,{level:`3`,id:`monitor-performance-metrics`,children:`Monitor Performance Metrics`}),`\n`,_jsx(_components.p,{children:`Regularly track metrics such as:`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsx(_components.li,{children:`API response times.`}),`\n`,_jsx(_components.li,{children:`Error rates.`}),`\n`,_jsx(_components.li,{children:`Request volumes.`}),`\n`]}),`\n`,_jsx(_components.p,{children:`Monitoring these metrics helps in identifying and addressing performance bottlenecks promptly.`}),`\n`,_jsx(Heading,{level:`2`,id:`input-validation-and-data-integrity`,children:`Input Validation and Data Integrity`}),`\n`,_jsx(Heading,{level:`3`,id:`validate-and-sanitize-inputs`,children:`Validate and Sanitize Inputs`}),`\n`,_jsx(_components.p,{children:`Ensure that all data sent to the API is properly validated and sanitized. This prevents malformed requests and potential security vulnerabilities.\\xA0`}),`\n`,_jsx(Heading,{level:`3`,id:`secure-data-storage`,children:`Secure Data Storage`}),`\n`,_jsx(_components.p,{children:`If your application stores data retrieved from the API, implement appropriate security measures to protect this data, especially if it includes personal or sensitive information.`}),`\n`,_jsx(Heading,{level:`2`,id:`nfrs-for-webhooks`,children:`NFRs for webhooks`}),`\n`,_jsx(_components.p,{children:`If your system subscribes to webhooks from Voyado Customer Engagement, your webhook listener must be designed for security, reliability, and scalability.`}),`\n`,_jsx(Heading,{level:`3`,id:`validate-the-source-of-incoming-webhook-requests`,children:`Validate the source of incoming webhook requests`}),`\n`,_jsx(_components.p,{children:`Each webhook request includes an HMAC-SHA256 signature in the x-voyado-signature header, based on a shared secret. Your listener should compute the expected signature and compare it to the one received to verify that the payload genuinely comes from Voyado. This protects against spoofed or unauthorized requests.`}),`\n`,_jsx(Heading,{level:`3`,id:`use-https-and-restrict-access-to-the-webhook-endpoint`,children:`Use HTTPS and restrict access to the webhook endpoint`}),`\n`,_jsx(_components.p,{children:`Only expose your listener over HTTPS to ensure data is encrypted in transit. Restrict access to your webhook endpoint by allowing only traffic from Svix’s external delivery IPs — the service Voyado uses to send webhooks. While not required, this is strongly recommended to reduce the risk of unauthorized access.`}),`\n`,_jsx(Heading,{level:`3`,id:`support-payload-encryption-if-required`,children:`Support payload encryption if required`}),`\n`,_jsx(_components.p,{children:`If sensitive data is to be included in some webhook payload, you can configure your webhooks to use AES-256 encryption. Your system must then decrypt the payload on receipt using your private key.`}),`\n`,_jsx(Warning,{children:_jsx(_components.p,{children:`Note that encryption is either enabled for all webhooks or disabled entirely.`})}),`\n`,_jsx(Heading,{level:`3`,id:`respond-quickly-and-asynchronously`,children:`Respond quickly and asynchronously`}),`\n`,_jsx(_components.p,{children:`Webhook endpoints should return a 200 OK response as soon as possible to acknowledge receipt. Any heavy processing (e.g., database updates or downstream API calls) should be handled asynchronously. This ensures reliable delivery and prevents timeouts.`}),`\n`,_jsx(Heading,{level:`3`,id:`implement-rate-limiting-and-queuing-if-needed`,children:`Implement rate limiting and queuing if needed`}),`\n`,_jsx(_components.p,{children:`If multiple events are sent in a short time (e.g., during peak campaigns), your listener should throttle incoming traffic and queue processing to avoid failures or overload.`}),`\n`,_jsx(Heading,{level:`3`,id:`log-and-monitor-webhook-activity`,children:`Log and monitor webhook activity`}),`\n`,_jsx(_components.p,{children:`Log incoming webhook events and responses, including timestamps, event types, and delivery status. Monitor for spikes, failures, or validation errors to ensure system health.`})]})}function MDXContent(props={}){const{wrapper:MDXLayout}={..._provideComponents(),...props.components};return MDXLayout?_jsx(MDXLayout,{...props,children:_jsx(_createMdxContent,{...props})}):_createMdxContent(props)}return{default:MDXContent};function _missingMdxReference(id,component){throw new Error(`Expected `+(component?`component`:`object`)+\" `\"+id+\"` to be defined: you likely forgot to import, pass, or provide it.\")}","frontmatter":{},"scope":{"config":{},"pageMetadata":{"title":"Non-functional requirements","description":null,"href":"/docs/api/non-functional-requirements"}}},"mdxSourceWithNoJs":null},"mdxExtracts":{"tableOfContents":[{"title":"Security best practices","slug":"security-best-practices","depth":2,"children":[{"title":"Use HTTPS with TLS 1.2 or higher for encryption in transit","slug":"use-https-with-tls-1-2-or-higher-for-encryption-in-transit","depth":3,"children":[]},{"title":"Store the API key securely at rest","slug":"store-the-api-key-securely-at-rest","depth":3,"children":[]},{"title":"Prevent exposure in logs or error messages","slug":"prevent-exposure-in-logs-or-error-messages","depth":3,"children":[]},{"title":"Use the correct header-based authentication","slug":"use-the-correct-header-based-authentication","depth":3,"children":[]},{"title":"Implement IP Filtering","slug":"implement-ip-filtering","depth":3,"children":[]}]},{"title":"Testing and environment readiness","slug":"testing-and-environment-readiness","depth":2,"children":[{"title":"Use the staging Environment","slug":"use-the-staging-environment","depth":3,"children":[]}]},{"title":"Resilience and Error Handling","slug":"resilience-and-error-handling","depth":2,"children":[{"title":"Implement appropriate retry logic","slug":"implement-appropriate-retry-logic","depth":3,"children":[]},{"title":"Recommended strategy","slug":"recommended-strategy","depth":3,"children":[]}]},{"title":"Monitoring and Logging","slug":"monitoring-and-logging","depth":2,"children":[{"title":"Comprehensive Logging","slug":"comprehensive-logging","depth":3,"children":[]},{"title":"Monitor Performance Metrics","slug":"monitor-performance-metrics","depth":3,"children":[]}]},{"title":"Input Validation and Data Integrity","slug":"input-validation-and-data-integrity","depth":2,"children":[{"title":"Validate and Sanitize Inputs","slug":"validate-and-sanitize-inputs","depth":3,"children":[]},{"title":"Secure Data Storage","slug":"secure-data-storage","depth":3,"children":[]}]},{"title":"NFRs for webhooks","slug":"nfrs-for-webhooks","depth":2,"children":[{"title":"Validate the source of incoming webhook requests","slug":"validate-the-source-of-incoming-webhook-requests","depth":3,"children":[]},{"title":"Use HTTPS and restrict access to the webhook endpoint","slug":"use-https-and-restrict-access-to-the-webhook-endpoint","depth":3,"children":[]},{"title":"Support payload encryption if required","slug":"support-payload-encryption-if-required","depth":3,"children":[]},{"title":"Respond quickly and asynchronously","slug":"respond-quickly-and-asynchronously","depth":3,"children":[]},{"title":"Implement rate limiting and queuing if needed","slug":"implement-rate-limiting-and-queuing-if-needed","depth":3,"children":[]},{"title":"Log and monitor webhook activity","slug":"log-and-monitor-webhook-activity","depth":3,"children":[]}]}],"codeExamples":{}},"jsLinks":["/_mintlify/custom/voyado/e3b0c44298fc1c14/js/test.js","/_mintlify/custom/voyado/a093bfac3f8a0766/js/gtm.js"],"subdomain":"voyado","siteName":"Voyado Docs","title":"Non-functional requirements - Voyado Docs","navMaps":{"firstHrefInVersion":{"API version 3":{"href":"/docs/api/non-functional-requirements","matchLevel":0},"API version 2":{"href":"/docs-v2/api-v2","matchLevel":2}},"firstHrefInLanguage":{}},"lastModified":"2026-10-30T13:58:42.946Z","originalFileLocation":"docs/api/non-functional-requirements.mdx"},"headHtml":"\u003cscript data-mint-head>(function(){var d=document.documentElement,p=null;if(!p){try{p=localStorage.getItem(\"isDarkMode\")}catch(e){}if(p==='true')p='dark';else if(p==='false')p='light';if(p!=='light'\u0026\u0026p!=='dark'\u0026\u0026p!=='system')p=\"light\"}var r=p==='system'?(matchMedia(\"(prefers-color-scheme: dark)\").matches?'dark':'light'):p;d.classList.remove(r==='dark'?'light':'dark');d.classList.add(r);d.setAttribute(\"data-theme-preference\",p)})();\u003c/script>\u003cmeta data-mint-head name=\"application-name\" content=\"Voyado Docs\">\u003cmeta data-mint-head name=\"generator\" content=\"Mintlify\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/voyado/9fqQ87AEVqs68vAz/_generated/favicon/android-chrome-192x192.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"apple-touch-icon\" type=\"image/png\" sizes=\"180x180\" href=\"/mintlify-assets/_mintlify/favicons/voyado/9fqQ87AEVqs68vAz/_generated/favicon/apple-touch-icon.png\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/voyado/9fqQ87AEVqs68vAz/_generated/favicon/favicon-16x16.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/voyado/9fqQ87AEVqs68vAz/_generated/favicon/favicon-32x32.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/voyado/9fqQ87AEVqs68vAz/_generated/favicon/favicon.ico\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/voyado/9fqQ87AEVqs68vAz/_generated/favicon-dark/android-chrome-192x192.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/voyado/9fqQ87AEVqs68vAz/_generated/favicon-dark/favicon-16x16.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/voyado/9fqQ87AEVqs68vAz/_generated/favicon-dark/favicon-32x32.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/voyado/9fqQ87AEVqs68vAz/_generated/favicon-dark/favicon.ico\" media=\"(prefers-color-scheme: dark)\">\u003cmeta data-mint-head property=\"og:title\" content=\"Non-functional requirements - Voyado Docs\">\u003cmeta data-mint-head property=\"og:site_name\" content=\"Voyado Docs\">\u003cmeta data-mint-head property=\"og:url\" content=\"https://developer.voyado.com/docs/api/non-functional-requirements\">\u003cmeta data-mint-head property=\"og:type\" content=\"website\">\u003cmeta data-mint-head property=\"og:image\" content=\"https://voyado.mintlify.app/_mintlify/api/og/v2?division=+\u0026amp;title=Non-functional+requirements\u0026amp;theme=d6be399f515d3ce011325711\u0026amp;signature=5edf0eb96bf1c51c2619abc3313791686ddff66286df2ec42898ba68b784ddfd\">\u003cmeta data-mint-head property=\"og:image:width\" content=\"1200\">\u003cmeta data-mint-head property=\"og:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"twitter:card\" content=\"summary_large_image\">\u003cmeta data-mint-head name=\"twitter:title\" content=\"Non-functional requirements - Voyado Docs\">\u003cmeta data-mint-head name=\"twitter:image\" content=\"https://voyado.mintlify.app/_mintlify/api/og/v2?division=+\u0026amp;title=Non-functional+requirements\u0026amp;theme=d6be399f515d3ce011325711\u0026amp;signature=5edf0eb96bf1c51c2619abc3313791686ddff66286df2ec42898ba68b784ddfd\">\u003cmeta data-mint-head name=\"twitter:image:width\" content=\"1200\">\u003cmeta data-mint-head name=\"twitter:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"msapplication-config\" content=\"/mintlify-assets/_mintlify/favicons/voyado/9fqQ87AEVqs68vAz/_generated/favicon/browserconfig.xml\">\u003cmeta data-mint-head name=\"apple-mobile-web-app-title\" content=\"Voyado Docs\">\u003cmeta data-mint-head name=\"msapplication-TileColor\" content=\"#fe4a49\">\u003clink data-mint-head rel=\"canonical\" href=\"https://developer.voyado.com/docs/api/non-functional-requirements\">\u003clink data-mint-head rel=\"alternate\" type=\"application/xml\" href=\"/sitemap.xml\">\u003clink data-mint-head rel=\"alternate\" type=\"text/markdown\" href=\"/docs/api/non-functional-requirements.md\">\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@graph\":[{\"@type\":\"Organization\",\"@id\":\"https://developer.voyado.com/#organization\",\"name\":\"Voyado Docs\",\"url\":\"https://developer.voyado.com\",\"logo\":{\"@type\":\"ImageObject\",\"url\":\"https://mintcdn.com/voyado/fiZZVymeDExCOKTK/logo/light.svg?fit=max\u0026auto=format\u0026n=fiZZVymeDExCOKTK\u0026q=85\u0026s=114e763881bcc646add31724b257f726\"}},{\"@type\":\"WebSite\",\"@id\":\"https://developer.voyado.com#website\",\"name\":\"Voyado Docs\",\"url\":\"https://developer.voyado.com\",\"publisher\":{\"@id\":\"https://developer.voyado.com/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https://developer.voyado.com/docs/api/non-functional-requirements#webpage\",\"url\":\"https://developer.voyado.com/docs/api/non-functional-requirements\",\"name\":\"Non-functional requirements\",\"dateModified\":\"2026-10-30T13:58:42.946Z\",\"isPartOf\":{\"@id\":\"https://developer.voyado.com#website\"},\"breadcrumb\":{\"@id\":\"https://developer.voyado.com/docs/api/non-functional-requirements#breadcrumb\"}},{\"@type\":\"BreadcrumbList\",\"@id\":\"https://developer.voyado.com/docs/api/non-functional-requirements#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\" \",\"item\":\"https://developer.voyado.com/docs/integration/about-integration\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Non-functional requirements\",\"item\":\"https://developer.voyado.com/docs/api/non-functional-requirements\"}]},{\"@type\":[\"Article\",\"TechArticle\"],\"@id\":\"https://developer.voyado.com/docs/api/non-functional-requirements#article\",\"headline\":\"Non-functional requirements\",\"name\":\"Non-functional requirements\",\"url\":\"https://developer.voyado.com/docs/api/non-functional-requirements\",\"mainEntityOfPage\":{\"@id\":\"https://developer.voyado.com/docs/api/non-functional-requirements#webpage\"},\"dateModified\":\"2026-10-30T13:58:42.946Z\",\"publisher\":{\"@id\":\"https://developer.voyado.com/#organization\"},\"isPartOf\":{\"@id\":\"https://developer.voyado.com#website\"}}]}\u003c/script>\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@type\":\"WebSite\",\"name\":\"Voyado Docs\",\"creator\":{\"@type\":\"Organization\",\"name\":\"Mintlify\",\"url\":\"https://mintlify.com\"}}\u003c/script>","siteName":"Voyado Docs","nav":{"global":null,"versions":[{"version":"API version 3","tag":"Latest","tabs":[{"tab":"Home","pages":[{"title":"Home","href":"/docs/introduction/about"}]},{"tab":"Integration","groups":[{"group":" ","pages":[{"title":"About integration","description":null,"href":"/docs/integration/about-integration"},{"title":"Non-functional requirements","href":"/docs/api/non-functional-requirements"},{"title":"Custom triggers","sidebarTitle":"Custom triggers","description":null,"href":"/docs/setup/custom-triggers"},{"title":"Stores","href":"/docs/setup/stores"}]},{"group":"Contacts","pages":[{"title":"Introduction to contacts","href":"/docs/contacts/about-contacts"},{"title":"Contact field definitions","href":"/docs/contacts/contact-field-definitions"},{"group":"Identify contact","pages":[{"title":"Find a contact","href":"/docs/contacts/identify-contact/find-contact"},{"title":"Get contact data","href":"/docs/contacts/identify-contact/get-contact-data"},{"title":"Get enriched profile","href":"/docs/contacts/identify-contact/get-enriched-profile"},{"title":"Soft identification","href":"/docs/contacts/identify-contact/soft-identification"},{"title":"Deep linking to contacts","href":"/docs/contacts/identify-contact/deep-linking"}]},{"title":"Create contact","href":"/docs/contacts/create-contact"},{"title":"Update contact","href":"/docs/contacts/update-contact"},{"title":"Delete contact","href":"/docs/contacts/delete-contact"},{"title":"Security best practices","href":"/docs/contacts/security-best-practices"},{"title":"Target audiences","href":"/docs/contacts/target-audiences"},{"title":"Consents","href":"/docs/setup/consents"},{"title":"Bulk contacts API","href":"/docs/contacts/bulk-contacts-api"},{"title":"NPS","href":"/docs/contacts/nps"}]},{"group":"Transactions","pages":[{"title":"Introduction to transactions","href":"/docs/transactions/introduction-to-transactions"},{"title":"Transaction field definitions","href":"/docs/transactions/transaction-field-definitions"},{"title":"Group currency","href":"/docs/transactions/group-currency"},{"title":"Register a purchase","href":"/docs/transactions/register-a-purchase"},{"title":"Register a return","href":"/docs/transactions/register-a-return"},{"title":"Discounts","href":"/docs/transactions/discounts"},{"title":"Purchase history summary","href":"/docs/transactions/purchase-history-summary"}]},{"group":"Orders","pages":[{"title":"Introduction to orders","sidebarTitle":"Introduction to orders","href":"/docs/orders/introduction-to-orders"},{"title":"Orders data structure","href":"/docs/orders/orders-data-structure"},{"title":"Orders API endpoints","href":"/docs/orders/orders-api-endpoints"},{"title":"Orders and actions","href":"/docs/orders/orders-and-actions"},{"title":"Order action schemas","href":"/docs/orders/order-action-schemas"}]},{"group":"Loyalty","pages":[{"title":"Introduction to loyalty","href":"/docs/loyalty/about-loyalty"},{"title":"Points","href":"/docs/loyalty/points"},{"title":"Point shop","href":"/docs/loyalty/point-shop"},{"title":"Vouchers","href":"/docs/loyalty/vouchers"},{"title":"Promotions","href":"/docs/loyalty/promotions"},{"title":"Challenges","href":"/docs/loyalty/challenges"},{"title":"Interactions","href":"/docs/loyalty/interactions"}]},{"group":"Product data","pages":[{"title":"Product feeds","href":"/docs/setup/product-feeds"},{"title":"Article register","href":"/docs/setup/article-register"}]},{"group":"Ratings and Reviews (BETA)","pages":[{"title":"About Ratings and Reviews","sidebarTitle":"About Ratings and Reviews","href":"/docs/ratings-and-reviews/about"},{"group":"Integrate Ratings and Reviews","pages":[{"title":"Installing the script","sidebarTitle":"Installing the script","href":"/docs/ratings-and-reviews/installing-script"},{"title":"The widgets","sidebarTitle":"The widgets","href":"/docs/ratings-and-reviews/widgets"},{"title":"Starting off","sidebarTitle":"Starting off","href":"/docs/ratings-and-reviews/starting-off"},{"title":"SPA handling","sidebarTitle":"SPA handling","href":"/docs/ratings-and-reviews/spa-handling"},{"title":"CSS Customization","sidebarTitle":"CSS Customization","href":"/docs/ratings-and-reviews/css-customization"}]},{"title":"Migrating Ratings and Reviews","sidebarTitle":"Migrating reviews","href":"/docs/ratings-and-reviews/migrating-reviews"},{"title":"Ratings and Reviews product data","sidebarTitle":"Product data","href":"/docs/ratings-and-reviews/product-data"}]},{"group":"Product Discovery","pages":[{"title":"Integration with Product Discovery","href":"/docs/product-discovery/integration"},{"title":"Boosting discoveryKey use","href":"/docs/product-discovery/discovery-key"},{"title":"Troubleshooting connected sessions","sidebarTitle":"Troubleshooting connected sessions","href":"/docs/product-discovery/connected-sessions"}]}]},{"tab":"File based transfer","pages":[{"title":"File based transfer","href":"/docs/file-based-transfer/about-file-based-transfer"}]},{"tab":"Data access","pages":[{"title":"Data access","href":"/docs/data-access/overview"}]},{"tab":"E-com and communication","pages":[{"title":"E-com and communication","href":"/docs/communication/communication"}]},{"tab":"Partner integrations","pages":[{"title":"Partner integrations","href":"/docs/introduction/about-partner-integrations"}]},{"tab":"What's new?","pages":[{"title":"What's new?","href":"/docs/introduction/whats-new"}]}]},{"version":"API version 2","tag":"Legacy","pages":[{"title":"API version 2","href":"/docs-v2/api-v2"}]}]},"basePath":"","title":"Non-functional requirements - Voyado Docs","buildId":"dpl_36EDyZCDfRuoUD9af5CH4M1bqY2h","deploymentId":"dpl_36EDyZCDfRuoUD9af5CH4M1bqY2h","dynamics":["10e4515597"],"chunks":["BannerMarkdown-DHaG7hr_.js","dist-DKZWslLH.js","lib-DuNMoZ05.js","lib-B8HbPgcC.js","clsx-BIWXJmsZ.js","html-void-elements-mCSPHezU.js","katex-YMM-FMeD.js","marked.esm-CZWythHk.js"]}