Public API and webhooks: questions for counsel
Prepared 2026-10-04 from the repository at 275ee94b. Not sent. This is the owner’s gate G1 (plan 2026-10-03-public-api-webhooks.md, spec section 10, owner decision D10).
Nothing here is a legal conclusion. Statements about the PDPA 2010 and its 2024 amendments are the author’s recollection, not read from the Act today, and are offered only so counsel can correct them. “Until answered” means what the software does now, which is the engineering team’s choice, not advice.
Sources: spec = docs/superpowers/specs/2026-10-03-public-api-webhooks-design.md; runbook = docs/RUNBOOK.md section 8.
1. What the feature is, in five lines
Section titled “1. What the feature is, in five lines”An organization on our ERP can (a) issue API keys (bearer credentials, prefix mck_) so its own software can read, and for a few document types create, its data over our public API, and (b) register webhook endpoints, URLs of its choosing, to which we POST a signed notification each time a catalogued event happens (for example sales_document.issued). Both are off for an organization until an administrator turns them on. We host in Singapore for testing now and intend Malaysian hosting for real customers (RUNBOOK section 1). Version 1 events are “thin”: identifiers, numbers and amounts, no HR, payroll or member data (spec 7.2, section 10). The read API can return personal data (for example party names) to whoever holds a key with the matching scope.
2. The acknowledgement text shipped today (provisional, version draft-1)
Section titled “2. The acknowledgement text shipped today (provisional, version draft-1)”Exact wording, English (web/src/lib/i18n/locales/en/integrations.json, keys ack.label and ack.provisional):
“I understand that data sent to a key’s holder or a webhook endpoint leaves this organization’s Malaysian hosting, and that the destination is mine or my processor’s.”
“This wording is provisional until counsel has reviewed it.”
Bahasa Malaysia and Chinese versions exist in the same folder (ms, zh) and are translations of the English; they have not been reviewed by anyone qualified, and counsel should treat the English as the only candidate text.
Who accepts and when. A person holding the administrator permissions integrations.settings.manage (turn the organization’s public API on), integrations.apikey.manage (create a key) or integrations.webhook.manage (create a webhook endpoint). A checkbox with that text must be ticked on each of three screens: turning the organization’s public API on, creating an API key, creating a webhook endpoint. Not on rotation, edit or replay. The accepting member’s id and the string draft-1 are written to the audit log with the action (audit_log.after->>'acknowledgement') and, for endpoints, stored on the endpoint row. The server only requires a non-empty version string; it does not compare it to a current version, and the web client sends the constant ACKNOWLEDGEMENT_VERSION = 'draft-1' (web/src/features/integrations/api.ts).
What it is meant to cover (spec 10): a record, in the customer’s own administrator’s hand, that the destination is theirs or their processor’s and that data sent there leaves our Malaysian hosting. It is a control and an evidence record. It was not drafted as a contractual term, a liability allocation or an acceptable-use policy, and the repository contains none of those for this feature.
3. Questions
Section titled “3. Questions”Each has: the system now, what an answer changes in code, and what happens until answered.
Q1. Is the acknowledgement wording sufficient, and in what form must it be given? (a) Is a one-line checkbox on three screens adequate evidence of the customer’s confirmation, or does it need to be part of the customer agreement or a separate data-processing term? (b) Should it say more: who the data user and processor are, that the customer is responsible for the receiving end, a link to full terms? (c) Is English-only authoritative, or must the Bahasa Malaysia version be reviewed as an equal?
- System now:
draft-1, three screens, audited, version not validated. - Answer changes: new wording replaces the three
ack.*strings in three languages and bumpsACKNOWLEDGEMENT_VERSION; if counsel wants a link to terms, a link element and a hosted page (none exists). - Until answered: stays
draft-1, labelled provisional on screen; the owner decides whether to enable the feature for any real customer.
Q2. Re-acceptance. If the wording changes, must existing administrators accept it again, and must existing keys and endpoints be suspended until they do?
- System now: no re-acceptance mechanism. Existing rows keep the version they were created under. No code compares versions.
- Answer changes: a new version would need (i) the server to reject a stale version, (ii) a stored “accepted version” per organization checked when a key authenticates or an endpoint is delivered to, and (iii) a screen prompting acceptance. That is new work (a migration and a check in the key and delivery paths); it is not just changing the string.
- Until answered: a version change would not affect existing keys or endpoints.
Q3. Liability for customer-configured webhook URLs. The customer chooses where we send notifications. If a customer registers a URL that is not theirs, or one that is compromised, or whose operator misuses what arrives, what is our exposure and how should the terms allocate it? Relatedly, who is responsible for forged or replayed deliveries received by the customer’s endpoint (we sign with HMAC-SHA256; receivers must verify; spec 7.4, runbook 8.2)?
- System now: we do not verify ownership of the URL. We refuse internal and reserved addresses (SSRF guard, spec 7.7) and send thin payloads only.
- Answer changes: possibly a URL ownership check (a challenge the receiver must answer, which is new build), wording in the acknowledgement, or a restriction on who may register endpoints.
- Until answered: as now.
Q4. PDPA position for data sent to customer endpoints and read through keys. Our design intent (spec 10) is that we are a processor for tenant data and the organization is the data user, that webhooks carry no personal data in v1, and that the read API can return personal data to a receiver abroad at the customer’s choice. (a) Is that characterization right? (b) As recalled, the PDPA restricts transfer of personal data outside Malaysia (section 129), the 2024 amendments revised the basis, and the in-force test and dates were recalled, not re-read: what is the current test, and does a customer’s key-holder abroad count as a transfer by us or by the customer? (c) Is a “thin event” containing, for example, a document number and a total amount, personal data when the customer is an individual or sole proprietor? (d) Do we owe any notification or record of our own (processor obligations, breach notification) beyond what the audit log and last_used_at/last_used_ip give?
- System now: no personal data in v1 webhook events (a contract test bans personal-data property names); the read API’s scopes are limited to permissions flagged grantable (spec 3.6); transfer is not blocked in code.
- Answer changes: if (c) is yes, event payloads shrink further or webhooks are barred for such customers; if the read API’s transfer needs a basis, a per-key destination declaration or an IP/region restriction, or removing some scopes from the grantable set.
- Until answered: HR, payroll and member data are excluded; the acknowledgement is the only transfer control.
Q5. Acceptable use. What prohibited uses and rights of suspension should the terms state (scraping, resale, load that harms other tenants, using keys from jurisdictions we cannot serve)? May we suspend a key or an organization’s public API without notice for abuse or security?
- System now: the platform operator can suspend an organization’s public API (
public_api_setting.operator_suspended, runbook 8). The operator console’s per-organization suspend action is not built; today it is a database update. A key can be revoked by the organization’s administrator or by us. - Answer changes: terms text; possibly a notice step before suspension; the console action.
- Until answered: suspension is possible, with no stated policy.
Q6. Rate limit and availability promises. Spec 6 sets defaults of 600 requests per minute per key, 3 000 per organization, and a failed-authentication limit of 20 per minute per source IP, with 429 and Retry-After. Should these numbers appear in terms, and must we avoid any availability or delivery-latency promise? Webhooks are at-least-once with retries and no ordering guarantee (D7, D8). Is “best effort, no service level” wording acceptable, and what should say about a delivery that ends dead?
- System now: no stated promise anywhere customer-facing; the rate limiter is built (confirm with the owner that the defaults are final).
- Answer changes: terms text and public documentation; limits are configuration.
- Until answered: no commitments made.
Q7. Key custody and security responsibilities. API keys are hashed at rest, shown once, expire (default 90 days, maximum 365), and act with the creating member’s current authority narrowed by scopes (D1, D2, D4). Webhook signing secrets are sealed with WEBHOOK_SECRET_KEY. (a) What custody duties should the customer accept (store securely, revoke promptly on suspected leak)? (b) Who bears loss from a leaked key used before revocation? (c) Are we obliged to notify the customer of anomalous key use, and within what time?
- System now: no automated notification of anomalous use; the evidence exists (audit rows naming “member via key”,
last_used_at/ip); runbook 8.1 tells our staff to tell the customer’s administrator. - Answer changes: acknowledgement wording; possibly an alert to the customer on a spike in failed authentication.
- Until answered: runbook procedure only.
Q8. Retention of the delivery log (spec 7.8, D5). Proposed: deliveries 30 days when succeeded and 90 days when failed or dead; events 30 days; idempotency records 24 hours; retired signing secrets deleted at once; response bodies never stored. Configuration changes (keys, endpoints, rotations, replays) live in audit_log under its own retention. (a) Is any of this a record we must keep for a statutory period (the architecture assumes a statutory minimum for accounting records; the webhook outbox is treated as operational, not a statutory record)? (b) Is the delivery log, which holds endpoint URLs and error classes, within a customer’s erasure or access request? (c) Is 30/90 days defensible, or should it be shorter or longer?
- System now: pruning is by the maintenance queue as above;
last_used_ipis kept as the last value only and cleared when the key is deleted. - Answer changes: retention constants and the prune job.
- Until answered: 30/90 days as above.
Q9. Versioning and deprecation. D6 proposes 12 months’ notice of removing or changing a public operation, with Deprecation and Sunset headers, and a compatibility check that stops an unreviewed breaking change from merging. Should we promise this to customers in writing? Can it be tied to additive-change freedom (new fields, new event types without notice)? Is 12 months right, or would 6 be acceptable?
- System now: the policy exists as design and a build check; nothing promised externally.
- Answer changes: terms text and documentation; the notice period is a constant in the deprecation tooling and the docs.
- Until answered: no external promise; engineering follows the 12-month rule internally.
Q10. Customer-facing documentation and notices. Is generated API documentation sufficient to carry the above, or do the terms need a separate signed schedule for customers who enable the feature? Where should the acknowledgement text sit relative to the main customer agreement?
4. Behaviour until answered (summary)
Section titled “4. Behaviour until answered (summary)”- Feature is off by default for every organization; an administrator turns it on and must tick the
draft-1box. - The screen says the wording is provisional.
- Test hosting is outside Malaysia and uses made-up data only, so no real personal data is being sent today.
- No terms, acceptable-use text, availability promise or deprecation promise exists for this feature.
- The owner should not enable it for a real customer before at least Q1, Q3 and Q4 are answered.
5. What each answer is likely to change in the repository
Section titled “5. What each answer is likely to change in the repository”| Answer | Change | Size |
|---|---|---|
| New acknowledgement wording only | three ack.* strings (en, ms, zh), ACKNOWLEDGEMENT_VERSION bump, test fixtures that use draft-1 |
small |
| Existing customers must re-accept | stored accepted version, server-side version check, prompt screen, behaviour for unaccepted keys and endpoints | medium, new migration |
| Verify endpoint ownership | challenge request at endpoint creation, new status | medium |
| Transfer limits on the read API | per-key destination declaration or restrictions, scope changes | medium to large |
| Different retention | constants in the prune job and retention_test.go expectations |
small |
| Customer notification of anomalous use | alert path in notifications | medium |
| Suspension policy | the operator console action (already an open gate) | small to medium |
| Terms, deprecation, rate-limit promises | documentation only | none in code |
6. A correction to carry
Section titled “6. A correction to carry”The spec’s D7 says “8 attempts over about 3 days”. The code (internal/platform/webhooks/backoff.go) makes one first attempt plus eight retries (waits of 10 s, 1 min, 5 min, 30 min, 2 h, 6 h, 12 h, 24 h, each jittered by 20%), about 45 hours in all; the code comment records the spec label as wrong. Use “nine attempts over about 45 hours” in anything sent to counsel or customers.