Skip to main content

14. Integrations

Two ways to connect this account to another system. API keys let a foreign system read and write here; webhooks let us tell a foreign system that something happened. Both live under Integrations, and both require the "manage API keys" permission, which only owners and administrators hold.

API keys

A key authenticates exactly as a person does — same addresses, same permissions. That is deliberate: there is no second, separate interface that will eventually behave differently from the portal.

curl https://invoice.tatwelletech.com/api/v1/portal/customers \
  -H "X-API-Key: twt_..."

Authorization: Bearer twt_... works just as well.

Three rules worth knowing:

The key is shown exactly once. There is no button to see it again — a key that can be read back ends up in a screenshot and a support ticket. Lost means replaced.

A key can never do more than the person who created it. You choose permissions when creating one, and only the permissions you hold yourself are offered. Choose as few as possible: a key for an inventory system usually needs no more than reading customers and creating invoices.

A key belongs to exactly one company. The header a person uses to switch companies does not move it.

Revoking takes effect immediately and permanently; the row stays visible, because a key that was used is part of the record.

Webhooks

Instead of asking repeatedly whether something happened, register an address and we will tell you.

Currently published: invoice.finalized, invoice.paid, incoming.received, incoming.approved, incoming.booked, customer.created.

https only, and the address must not be on an internal network — checked when you save it, and again on every delivery, because a DNS record can change afterwards.

Delivery and retries

Delivery happens in the background, never while you work: whether your server answers right now must not decide how long finalizing an invoice takes.

If your server does not answer with a 2xx we try again — after one minute, then two, four, eight, up to six hours apart, eight times in all. That spans an overnight outage without overwhelming anybody. After that the delivery counts as failed, but it does not disappear: you see it in the log with its last error and can put it back on the queue with Send again once your system is running.

Verifying the signature

Every message carries X-TWT-Signature: t=<timestamp>,v1=<signature>. The signature is HMAC-SHA256 over <timestamp>.<body>, using the signing secret you saw once when creating the endpoint.

mac = hmac.new(secret.encode(), f"{ts}.{body}".encode(), hashlib.sha256).hexdigest()

Always verify the signature, and reject messages whose timestamp is well in the past — otherwise a message captured once could be replayed indefinitely. We never follow redirects.

The X-TWT-Delivery header carries a unique id. Use it to avoid processing the same message twice: we retry on failure, and a message you processed but did not acknowledge will arrive again.