
Securing your Zid app: webhook auth, token protection, minimal scopes and TLS
Verify every webhook with Basic Auth, keep tokens and secrets on your server, request only the scopes you use, and pass Zid's TLS checks before review.
Where security shows up in review
Zid's App Activation & OAuth Policy sets security requirements that apply to every new app submitted to the App Market, whether it uses standard activation or partner-managed setup. Zid's team reviews them. An app that doesn't comply may be returned for correction, rejected, suspended until corrected, or removed when the flow creates material security, privacy, usability or merchant-trust risk.
This guide covers what you control in your own code and infrastructure: incoming webhooks, stored tokens, requested scopes, your endpoints' TLS setup, and the callback step. The OAuth flow itself and webhook subscriptions are covered in our OAuth and webhooks guides.
GET v1/managers/profile was retired in favour of Account API endpoints with separate scopes.Verify every webhook with Basic Auth
When you create a webhook with POST /v1/managers/webhooks, you can pass a username and password. Zid joins them as username:password, Base64-encodes the result and sends it with every delivery in an Authorization: Basic header. According to the partner changelog, you must provide these credentials when creating or updating any webhook, as Basic Authentication has been mandatory for all webhooks since 30 September 2026.
POST /v1/managers/webhooks
Authorization: Bearer <Authorization token>
X-Manager-Token: <access_token>
{
"event": "order.create",
"target_url": "https://hooks.example.com/zid/orders",
"original_id": "<your identifier>",
"username": "zid-hook",
"password": "<long random value>"
}On your server, reject any request whose header doesn't match before you read the body or act on it:
import crypto from 'node:crypto';
function isFromZid(req, expectedUser, expectedPass) {
const header = req.headers['authorization'] || '';
const expected = 'Basic ' +
Buffer.from(`${expectedUser}:${expectedPass}`).toString('base64');
const a = Buffer.from(header);
const b = Buffer.from(expected);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}- A good practice is to generate a long random password per store and keep it with that store's other secrets, so one leaked value doesn't expose every merchant.
- Compare the values in constant time, as in the example, and return
401without processing when the check fails. - Hold this endpoint to the same HTTPS and TLS standards as the rest of your app (see below).
Headers for app events and shipping webhooks
App subscription events, such as app.market.application.install and app.market.application.uninstall, aren't created through the API. You configure them in the Webhooks section of your app in the Partner Dashboard, where the App Management docs let you specify additional headers for authentication or other requirements. Shipping apps work the same way: Zid creates the webhooks, and you fill in the target URL and header.
Use that option. Put a secret value in a custom header and reject any request to your app-events endpoint that doesn't carry it. The header name below is only an example:
POST /zid/app-events HTTP/1.1
Host: hooks.example.com
Content-Type: application/json
X-Example-Secret: <long random value>app.market.application.authorized includes merchant_email and merchant_phone_no, so treat this endpoint and its logs as personal data.Pass Zid's TLS checks
The Partner Dashboard validates every webhook or endpoint URL before accepting it. If it can't establish a secure and timely connection to your server, you see an Invalid TLS URL error. The help centre lists five causes:
- Old TLS version: only TLS 1.2 and TLS 1.3 are supported. TLS 1.0 and 1.1 are rejected.
- Invalid certificate: expired, self-signed, missing intermediate certificates, or not matching the domain. Use a certificate from a recognised Certificate Authority.
- Slow handshake: your server must acknowledge the connection and complete the TLS handshake within 5 seconds.
- Unreachable server: DNS problems, firewall rules, access control lists, or DDoS protection and WAF rules blocking incoming traffic.
- Plain HTTP: the URL must start with
https://.
Test the URL before you save it:
curl -Iv https://your-server.com/endpointIn the output, look for SSL connection using TLSv1.2 or TLSv1.3, make sure there's no SSL certificate problem line, and check that the connection completes well under 5 seconds. The OAuth policy also requires HTTPS for your production launch, callback, onboarding and API endpoints.
Protect tokens and refresh them on time
The token response gives you an Authorization value (API access) and an access_token that you send as X-Manager-Token (access to one specific store). The docs say this data must be kept in secure storage and that abusing a token might get your app blocked. The policy adds specific rules:
- Store tokens on the server, and restrict who and what can read them.
- Never expose access tokens, refresh tokens or client secrets in page content, URLs, client-side storage, analytics or logs.
- Keep the client secret out of browsers, mobile apps, public repositories and client-side code.
- Keep each authorized store isolated from every other store and partner account.
- Support token renewal, reauthorization and reinstall without creating duplicate or cross-store connections.
- After uninstall or revocation, stop processing and remove the applicable access.
Tokens last one year, and the docs recommend refreshing around the 10-month mark. A refresh token is single-use, so store the new one after every refresh. A good practice is to run refresh as a scheduled job that alerts you when it fails, instead of waiting for a 401 in the middle of a merchant's work. If Zid reports that the refresh token is invalid, the merchant has to reactivate the app to start OAuth again.
Authorization, X-Manager-Token and Access-Token headers from your request logs.Request only the scopes you use
The policy requires you to request only the scopes needed for your app's documented functionality. You select them on your app page in the Partner Dashboard, and the team reviews them when you submit the app for publishing.
The new Account API shows why this matters. It replaces GET v1/managers/profile, retired on 30 September 2026, with endpoints that each have their own scope:
third_account_identity_read: basic account identity information.third_account_profile_read: extended profile data, including birthdate, job title and geographical information.third_store_details_read: store details, branding, localization, social accounts, operations and business information.
If all you need is to know who the account is, request the identity scope and leave out the extended profile. The same rule applies across the API: every endpoint page lists its scope, such as third_webhook_write for creating webhooks, so tie every scope you request to a feature that actually uses it.
OAuth callback hygiene
The callback is where an attacker would try to inject a code or bind the wrong store. That's why the policy requires you to:
- Generate a random, one-time, short-lived
state, bind it to the session that started the request, and validate it before accepting the callback. - Use PKCE where your chosen client configuration supports it, and wherever Zid requires it.
- Exchange the code promptly on your server, then remove it before any onward navigation. Never log it, display it or forward it into other URLs.
- Handle denied, cancelled, expired, invalid and replayed attempts safely, without creating incorrect or duplicate connections.
- Never link a store to an existing external account only because email addresses match.
- For embedded apps, validate Zid's embedded authentication before serving any protected content. A store identifier in a launch URL doesn't authenticate the merchant.
If activation fails, the help centre points to two common causes: OAuth Authorization Server Error usually means the redirection URL or callback URL is wrong, and Internal server error - OAuth is your own server's response to Zid's request during the redirection step, so check your server logs.
Key takeaways
- Set a
usernameandpasswordon every webhook, and reject deliveries whoseAuthorization: Basicheader doesn't match. - Add a secret custom header to your app-event webhooks in the Partner Dashboard.
- Serve every endpoint over HTTPS with TLS 1.2 or 1.3, a valid certificate and a handshake under 5 seconds.
- Keep tokens and the client secret on the server, out of URLs and logs, and refresh before the year is up.
- Request only the scopes your features use, and validate
stateon every callback.
Sources
- Create Webhook
- [Docs Update - Apps] Action Required: Webhook Security Changes by September 30, 2026
- [Docs Update - Apps] Action Required: Upcoming API Changes by September 30, 2026
- App Management Events
- How to create webhooks for my app ?
- Invalid "TLS URL Error" in the Partner Dashboard
- Authorization
- Zid App Activation & OAuth Policy
- Create a public app
- The refresh token is invalid
- OAuth Authorization Server Error
- Internal server error - OAuth
Start building on Zid
Sign up in the partner portal, then build and test on a development store.


