Skip to content

Security & threat model

What is a threat model? It describes what is to be protected, who might attack it, in which ways that could happen, and which protections stand against it. It also states honestly what is not covered, or only partly. That lets you judge as a customer what you entrust to us and what you have to contribute yourself.

1. What we protect

AssetWhy it matters
Your organization’s dataTenants, contracts, garages, documents and emails may only be seen by your integrations, never by another organization.
TokensA token is the key to your data within the scope of its permissions.
Webhook secretsWith the secret, a receiver can verify that a message comes from us; whoever knows it could forge messages.
Booking and payment dataFinancial data is particularly sensitive; bank and payer details are only available with an additional permission.
AvailabilityThe API and webhook delivery should stay usable for everyone, even with misuse or mass requests.

2. Who might attack, and what we assume

  • Another organization (a foreign tenant) trying to reach data or ids of your organization.
  • Whoever holds a token — stolen, expired or already revoked — and misuses it.
  • A malicious webhook receiver, or someone who tries to make us contact internal systems through a webhook address (SSRF), or who forges or replays messages.
  • A network attacker who reads or alters traffic.
  • Abuse through mass requests, deliberate or caused by a bug in an integration.
  • Operational mistakes, such as a wrong setting or a secret passed on by accident.

We assume an attacker knows the public documentation and the OpenAPI description, but neither your tokens nor your webhook secrets. Not covered is an attacker who already has access to your own systems or to your account in the management app: whoever holds a valid token may do what its permissions allow.

3. Threats and protections

The tables list for each area the threat, the protection and the kind of check. Figures and limits are on the respective pages of the documentation.

Authentication and tokens

ThreatProtectionHow we check it
Token is stolen from the databaseOnly a keyed hash of the secret is stored, never the secret itself; the key needed for it is not in the database. The token is shown only once.Automated tests, database checks
Token is too powerfulEach token carries only the permissions (scopes) you granted; the endpoints with data require the matching scope. The token only works as long as its creator still has the API access permission in the management app.Automated tests, database checks
Stolen or expired tokenTokens have an expiry date. Revocation takes effect from the next request; the same applies when the organization’s access is switched off. Creating and revoking a token are recorded in the audit log.Automated tests, database checks
Guessing tokensThe secret is random and very long. In addition, limits per address slow down failed sign-in attempts; if the counter needed for that fails, the API refuses instead of answering unthrottled.Automated tests
Token in the browser or in linksA token in the address (query string) is rejected; cookies are not evaluated. The API is intended for server-to-server calls.Automated tests

Tenant isolation and responses

ThreatProtectionHow we check it
Access to another organization’s dataThe organization comes from the token, not from details in the request. Queries run through the API’s database functions, which verify the token and the organization themselves (design rule); this check is the decisive boundary. Row-level security (RLS) of the tables protects the other access paths, such as the management app, but is not a second protection layer of the API. Where the API provides files (document and DATEV download links), a short-lived link for a path of your own organization is only created after the check in the database.Automated tenant-isolation check during development, database checks
Trying ids of foreign objectsFor single resources, unknown and foreign ids are answered the same way with 404, not with “no permission”. For the bulk lookups via by-ids, the API answers 200 and simply leaves out unknown, foreign and invisible identifiers. In both cases the API does not reveal whether an id exists elsewhere.Automated tests
Internals in error messagesErrors have a fixed format with an error code and request_id. An internal error (500) gives no details; responses are not cached.Automated tests

Abuse and repetition

ThreatProtectionHow we check it
Mass requestsLimits per address, per token and per organization; the token limit counts reading and writing calls separately; the sandbox has tighter limits. See the “Limits” page.Automated tests
Duplicate bookings through retriesWriting calls usually take an Idempotency-Key: if you repeat the same request with the same key, the result is returned instead of creating something twice. How long the key is kept and what it is bound to depends on the endpoint: for most it is the token (after a token change a new operation can be created), for some (such as imports, appointments, DATEV exports) the organization; taking over an import needs no key because it can safely be repeated by itself. After the retention period a late retry can start a new operation. The binding table per endpoint is under idempotency.Automated tests
TraceabilityCalls are logged (without the token and without contents), including rejected sign-ins with a known token identifier; you can see the history per token in the management app. The log is a best-effort aid and can be incomplete (for example with very many rejected sign-ins or during faults of the log).Automated tests, database checks

Webhooks

ThreatProtectionHow we check it
Forged messageEvery message is signed (HMAC-SHA256 over the timestamp and the unchanged raw body). The receiver should check the signature with a constant-time comparison. Example code is in the webhooks handbook.Shared test vectors for all examples, automated tests
Replayed messageWe deliver a signed timestamp and a constant event id. The receiver protects against replay: it must reject messages older than 5 minutes and store processed event ids. The server does not enforce this.Automated tests
Secret becomes known or is lostThe secret is stored only in encrypted form and shown only once. Rotation is possible at any time; the previous secret remains valid for another 24 hours so you can switch over without an outage. Only the immediately preceding secret stays valid: if you rotate a second time within these 24 hours, the transition period of the first secret ends immediately.Automated tests
Webhook address as a lever against internal systems (SSRF)Only https on port 443, only public targets. The address is checked on creation and on every send; the connection goes to exactly the checked addresses so that a later DNS change (DNS rebinding) changes nothing. Redirects are not followed, the receiver’s response content is not stored, and no internal tracing headers are sent along.Automated tests
Lost or duplicate messagesRetries with a constant event id, detection of duplicate events, resending (replay) of failed deliveries, pausing instead of discarding when switched off, reconciliation through the list endpoints. This is a reliability topic and one of data integrity at the same time — see the limits below.Automated tests, database checks

Sandbox, status and management

ThreatProtectionHow we check it
Test and real data get mixedThe sandbox is its own organization with invented sample data. A test token only sees this data, and a real token never sees the sandbox. The assignment is enforced in the database; if it does not match, the token is rejected. Responses to test tokens carry the X-M2H-Sandbox header.Automated tests, database checks
Customer data on the status pageThe status page only shows aggregated values (state, availability, incidents, a few metrics) without details of individual customers, organizations or tokens.Automated tests, database checks
Abusing token management from outsideCreating and revoking tokens works only when signed in to the management app and only with the API access permission. The management app is separate from the API host: the API host has no management pages.Automated tests
Secrets in logsThe call log contains neither tokens nor contents; network error messages are mapped to fixed codes. Error monitoring masks known fields that carry credentials.Automated tests

4. Known limits and accepted residual risks

  • Webhooks are not “exactly once”. Messages can arrive more than once, in a different order or late, and in rare cases an event can get lost. Process idempotently and reconcile regularly through the lists (see known limits of webhooks).
  • The status page measures inside the same service it observes. If the whole service fails, the page itself may be affected. It does not replace monitoring of your own integration.
  • A valid token is a key. Whoever holds it may do everything its permissions allow until it is revoked or expires. We cannot tell whether the caller is “really” your integration.
  • No independent attestation. There is currently no independent penetration-test certificate and no certification (such as ISO 27001 or SOC 2) for the API. The checks listed below are our own automated and AI-assisted checks — they do not replace an independent audit.
  • The organization check of the database functions is a design rule. Whether all functions of the API call it is verified in review; an automated test for it is still missing.
  • Not every check targets the API specifically. Some of the general security checks run for the whole application, not specifically against the external API. The API’s database is additionally covered by its own checks.

5. What you should do

  • Give each token only the permissions the integration really needs, and create a separate token per integration.
  • Keep tokens and webhook secrets in a password manager or secret store — never in websites, browser apps, emails, chats or Git.
  • Always verify the webhook signature, over the unchanged raw body, not over re-serialized JSON. Compare in constant time.
  • Reject messages with a timestamp that is too old (5 minutes) and process each event id only once.
  • Rotate webhook secrets regularly and immediately if one may have become known; revoke tokens you no longer need.
  • Operate webhook receivers only over https on port 443 with a valid certificate and a publicly reachable address.

More under Security & support, Authentication and Webhooks.

6. How we check

We use several methods, each covering part of the threats above. They are our own checks and not a seal of approval from an independent body:

  • Automated tests for tokens, permissions, signature, address checking, delivery and documentation (run on every change).
  • An automated tenant-isolation check that tries to read one organization’s data from another.
  • Database checks (“probes”) against the API’s database, also on a schedule.
  • Static code analysis and dependency, secret and container scans in automated quality assurance (including Sonar, Semgrep, Trivy, gitleaks).
  • Security reviews of the code, also AI-assisted, for larger changes to the API.
  • General automated attack tests of the application (including SQL injection, API fuzzing, web scanners).

7. Reporting a vulnerability

Please report security concerns and vulnerabilities by email to security@manage2hero.com. The address is also in our security.txt (/.well-known/security.txt). We review your report and get back to you with any questions.

A report should contain:

  • the affected endpoint or function
  • the steps to reproduce the problem
  • the suspected impact
  • the request_id of the response concerned, if there is one

Please do not:

  • send a token or secret — not even to demonstrate the problem
  • send or collect customer data
  • access other organizations’ data to prove the vulnerability
  • run load tests or denial-of-service attempts against production

If you have lost a token, first revoke it immediately in the management app (see Security & support).

Which changes we announce and how long old versions keep running is described under Versioning; changes to the security description appear in the changelog.