API Key Security
Storage
Do:- Store keys in environment variables
- Use secrets management services (AWS Secrets Manager, Vault, etc.)
- Rotate keys periodically
- Commit keys to git
- Include keys in frontend bundles
- Share keys via unencrypted channels
Key Types
Making, Rotating and Deleting Keys
You make and manage your own keys. In the dashboard, open Workspace settings → API keys. With the API, use the key calls below.{id} is your own account id, which GET /tokens/validate returns as data.userId. Your plan must include API access.
GET /users/{id}/keyslists your keys, with what each is limited to (presetandscopes).POST /users/{id}/keysmakes one (send anIdempotency-Key). Only a person signed in on widgetic.com can make a key, so make keys in the dashboard: a request made with an API key or an SDK token is refused with403. The secret is in the answer once, inkey, and cannot be shown again. Send apreset(read_only,contentorfull_access) or a list ofscopesto limit what the key can do, and neither for a key with full access. See Limit what a key can do.PUT /users/{id}/keys/{keyId}renames a key or switches it off withisActive: false. Switching a key back on needs a person signed in too.DELETE /users/{id}/keys/{keyId}deletes it.GET /users/{id}/api-keys/{keyId}/logsshows what a key was used for.
Client-Side Authentication
A browser must never hold an API key (wgt_usr_, wdg_org_, or wgt_prt_). Keep your API key on your server and have your backend make API calls on behalf of your application, or—when mounting Widgetic’s editors in your page for one of your customers—mint a short-lived editor session on your server:
Domain Validation
You can limit the websites an embedded widget may appear on. If you list nothing, the widget works everywhere.- Open Allowed Domains in the dashboard (for a team workspace, use that workspace’s settings; only its admins and owners can change the list)
- Add each website:
example.comalso coverswww.example.com, and*.example.comcovers the domain and every subdomain - Leave
localhostout: a page on your own machine always works, however short the list is (see below)
{id}. GET /users/{id}/workspace/domains reads it, POST adds a website with the body { "domain": "example.com" }, PUT /users/{id}/workspace/domains/{domainId} changes one, and DELETE removes one. A wildcard such as *.example.com is one entry, however many subdomains it covers. The list of an ordinary account holds up to 50 websites, and the 51st answers 409 with the code BUSINESS_STATE_CONFLICT. If you administer a partner account, your list has no limit: your customers may publish on their own domains, and you can add each of those by code, one POST for each website. A change applies within about 15 seconds.
Once a list exists, the lookup that returns a widget’s content answers 403 with the code DOMAIN_NOT_ALLOWED to a page on any other website, and the widget’s frame page asks the browser not to show it anywhere else. A very long list, which only a partner can have (more than about fifty websites of ordinary length), does not fit in that request to the browser, so the frame page is then shown without it; the lookup, and the entries a visitor sends, still refuse every website that is not on the list. Widgetic’s own sites always work, and so does your own machine. If you are a partner, your platform’s website always works too (see below). The lookup never refuses a page on localhost, on 127.0.0.1 (or any address from 127.0.0.0 to 127.255.255.255), on [::1] or on a name under .localhost, on any port, and the widget’s frame page may be shown by localhost, 127.0.0.1 and names under .localhost, on any port, over http or https. You can build and test an integration without listing your laptop: only the person who runs such a page can see it, so listing it would protect nothing. A list that cannot be read never blocks a widget.
The frame of a workflow, and its oEmbed card, follow the list of the owner of the workflow’s first composition in the same two ways: the frame asks the browser not to show it on another website, and the card answers 403 there. See Show a workflow where you cannot add a tag.
If you use the Partner API, the allowedDomains of your partner account is added to the list of the person who administers it, and applies to the widgets made with your API keys.
Your platform is always allowed
If you are a partner, the website where you integrate Widgetic is always allowed, with all of its subdomains. Tell Widgetic which website that is when your partner account is set up, for exampleacme.com, and Widgetic records it as your platform domain. acme.com and every subdomain of it (app.acme.com, learn.eu.acme.com) can then always show your widgets, and the entries their visitors send (such as votes or form answers) are always accepted from them, whatever your list holds. The widgets your customers make through you are covered too, because they belong to your account.
- You never have to list it, and a list can never lock it out. It is not an entry of your list, so adding websites to the list, or removing them all, never changes it.
- It never narrows anything. If you list nothing, your widgets still work on every website, not only on your platform.
- Widgetic sets it, you read it.
GET /partners/minegives it asplatformDomainfor each partner you administer, and it isnulluntil Widgetic has set one. To change it, write to partners@widgetic.com. - It is one website. Give Widgetic the bare name, such as
acme.com; an address likehttps://www.acme.com/pricingis shortened to it. A different website that also shows your widgets, such as a separate marketing site, is not covered: put it on your list when you keep one.
A domain list keeps your widgets off other people’s pages. It is not access control: the content of a published widget can still be requested by anyone who has its ID, using a plain HTTP request that names no website for the check to read. Do not rely on it to keep content private.
The
<widgetic> embed script stops at the refusal: it draws nothing, sets data-widgetic-refused="DOMAIN_NOT_ALLOWED" on the element, and writes one warning to the browser console for each widget. It does not fall back to a copy of the widget on Widgetic’s CDN.Webhook Signatures
Partner accounts can have Widgetic call their own server when something happens, for example when an invoice is paid. No other kind of account receives webhooks. Every call is signed, with the Standard Webhooks headerswebhook-id, webhook-timestamp and webhook-signature, under a secret that only you hold.
Before you act on a call:
- Check the signature over the raw body, with a Standard Webhooks library or the short check in Notifications.
- Refuse a call whose
webhook-timestampis more than five minutes old. - Keep the
webhook-idof every event you handled and drop a repeat: delivery is at least once.
POST /partners/{partnerId}/notifications/webhook-secret. The old one keeps signing for a day, so your receiver can switch without a call being rejected. See Your Own Webhook.
Rate Limiting
Protect your integration from abuse:Data Protection
Sensitive Data
Don’t store sensitive user data in widget configurations:Audit Logging
Log API interactions for security auditing:Your Customers’ Data
If you keep managed users for your customers, Widgetic holds a record for each one. You can read a record and delete it:managedUserId label. To erase a customer’s content as well, list it with the managedUserId filter and delete it first, then delete the record (see Managed Users).
Security Checklist
API Key Management
API Key Management
- Keys stored in environment variables
- Keys rotated quarterly
- Separate keys for dev/staging/production
- Access logged and monitored
Client Security
Client Security
- Token exchange for browser apps
- Short token expiration (a token lasts at most 1 hour)
- Domain validation enabled
- Partner accounts: your platform domain is set (
platformDomaininGET /partners/mine) - CSP headers configured
Webhooks (partner accounts)
Webhooks (partner accounts)
- Signature checked on the raw body
- Calls older than five minutes refused
- Repeats dropped by
webhook-id - A
2xxanswer within ten seconds, with the slow work done afterwards - Secret kept out of git, and replaced if it leaks