€ Account & billing · 10 min read
Connecting Zeitflex to other systems: API keys and webhooks
A till system that wants to know the week’s staffing. A payroll bureau that should find out when the month has been closed. Your own report that reacts to every clock-in. For this, there are two tools under Settings → API & webhooks. This page says honestly what each of them does today — and where the interface deliberately stops.
Who sees the page and what it costs
API keys and webhooks are part of the Pro plan. On the Free plan and the Starter plan, the tab shows a note about the subscription instead of the setup. During the trial, Pro applies, so this feature does too. Only owners and managers may use it; employees do not see the tab. What else distinguishes the plans is described in Plans and prices.
Webhooks: Zeitflex calls your system
A webhook is an HTTPS address in your system that Zeitflex calls as soon as something has happened. This is how you set one up:
- Open Settings → API & webhooks.
- Under New webhook URL (HTTPS only), enter your system’s address. Zeitflex does not accept addresses without HTTPS.
- Select at least one event and create the endpoint.
- Copy the signing secret (starts with
whsec_) immediately. It is shown exactly once and cannot be retrieved afterwards. If it is lost, create the endpoint again.
Later, you can change the address and events, temporarily switch the endpoint off or delete it. Each of these steps is recorded in the business’s log.
The five events
| Event | When it is triggered | What is in data |
|---|---|---|
time_entry.created | Clock-in, start or end of a break in the browser (ending a break reports the new work entry that begins after it), clock-in at the wall terminal (including when delivered later after being offline), late entry by management | Entry ID, person ID, start, source; in the browser and for late entries also the type (work or break); for late entries also the end and nachgetragen: true; for later delivery from the terminal also offlineNachgeliefert: true and whether the PIN was checked |
payroll_period.locked | A month was closed and locked | Period ID, year, month, number of line items, time of locking |
absence.approved | Management approved an absence request — or entered an absence directly, without a prior request (for example a sick note phoned in) — or a person reported themselves sick (effective immediately, no approval) | Absence ID, person ID, type (sickness and child sick as the neutral type ABWESENHEIT), from, to, half-day; for a direct entry also weg with the value EINGETRAGEN, for a person's own sick report with the value SELBST_GEMELDET |
absence.cancelled | An absence was withdrawn or reversed by management | as above, plus weg (ZURUECKZIEHEN or RUECKNAHME) and the previous status |
shift_plan.published | A shift plan was published for a week | Monday of the week, number of shifts, number of open shifts |
Each delivery is a JSON document with id, type, createdAt, organizationId and the data block. What is never in it: names, wages, premiums, GPS coordinates, the reason text of a reversal (for a sick note it may contain health data) and files. The types “Sickness” and “Child sick” are health data and are therefore sent only as ABWESENHEIT; vacation, time off in lieu and the other types appear as usual. Your system receives IDs and works with what it already knows about the person.
How to verify authenticity
Every delivery carries the header entry Stempo-Signature: t=<Unix seconds>,v1=<hex>. The v1 value is an HMAC-SHA256 over the string <t>.<raw JSON text> with your signing secret as the key. Recalculate it on your side and discard anything that does not match. The header name dates from the time when the product was called Stempo, and deliberately stays that way — renaming it would silently break every existing integration. The user agent is accordingly Stempo-Webhooks/1.0.
What happens if your system does not respond
Zeitflex attempts delivery three times: immediately, after 0.5 seconds and after 2 seconds. Each attempt is aborted after 5 seconds. If your system responds with a 4xx class error (except 408 and 429), there is no further attempt — the request is considered permanently rejected. Zeitflex then records the failed attempt in the log, and that is it.
An unreachable endpoint changes nothing about the data: the clock-in, approval or month-end close is saved before the first delivery attempt begins. The response to the person who is clocking in or approving at that moment may, however, be delayed by the duration of the delivery attempts — Zeitflex waits for the three attempts before it responds.
API keys: fetching the times of a month
Under New API key, you assign a name (2 to 60 characters) and choose Read, Write or both. The key starts with stpo_, is shown once and never again — Zeitflex only stores a SHA-256 hash and, for recognition, the prefix stpo_ together with the first eight characters after it. A key can be revoked at any time; creation and revocation are recorded in the log.
A key with the Read permission opens exactly one address: your business’s times for one month. Your program sends the key in the Authorization header, for example like this:
curl -H "Authorization: Bearer stpo_YourKey" \
"https://app.zeitflex.de/api/v1/zeiten?monat=2026-09"
The response is JSON. For each person it contains the identifier, the name and the personnel number, and below that the working days of the month with gross, break and net minutes and the entries of each day (start, end, work or break, approval status, recording route). The minutes are calculated by the same component as the timesheet and the month-end close — statutory break and your business’s rounding per working day. The field names are German. This is what a response looks like (shortened, one person with one working day from 8 am to 4:30 pm without a recorded break):
{
"monat": "2026-09",
"zeitzone": "Europe/Berlin",
"rundung": "NONE",
"personen": [
{
"kennung": "cmg1u4n8r0002qv8hx5y6z7w8",
"name": "Anna Abel",
"personalnummer": "17",
"summe": { "bruttoMinuten": 510, "gestempeltePausenMinuten": 0,
"autoPausenAbzugMinuten": 30, "nettoMinuten": 480 },
"tage": [
{
"datum": "2026-09-01",
"bruttoMinuten": 510,
"gestempeltePausenMinuten": 0,
"autoPausenAbzugMinuten": 30,
"nettoMinuten": 480,
"eintraege": [
{ "kennung": "cmg2x7k1p0004qv8h1a2b3c4d", "typ": "ARBEIT",
"beginn": "2026-09-01T06:00:00.000Z", "ende": "2026-09-01T14:30:00.000Z",
"status": "GENEHMIGT", "quelle": "STEMPELUHR" }
]
}
]
}
]
}
In English: personen = people, tage = days, eintraege = entries, kennung = identifier, beginn/ende = start/end, bruttoMinuten = gross minutes, gestempeltePausenMinuten = recorded break minutes, autoPausenAbzugMinuten = statutory break deducted automatically, nettoMinuten = net minutes, typ = ARBEIT (work) or PAUSE (break). Start and end are in UTC (the Z at the end); datum is the day in German time. A few rules:
- The month is based on German time. A shift counts towards the day and month in which it began: the night shift from the 30th to the 1st appears in the old month.
- Only completed times in their current version are delivered. A running clock-in appears once the person has clocked out; a corrected time appears only in its new version; a time that was not recognised does not appear at all.
- Whether a time is paid is still decided by the month-end close. You see the approval status of each entry in the
statusfield:OFFEN(open),EINGEREICHT(submitted) orGENEHMIGT(approved). - The
quellefield names the recording route:STEMPELUHR(wall terminal),WEB(clocked in the web app),API(sent from the mobile app, or clocked in the browser without a connection and delivered later — never via this interface),MANUELL(added or corrected by hand) orIMPORT(via the import). Let your program also accept values that are new to it — the list can grow. - No pay data: no hourly rates, no gross pay in euros, no supplements — and no location data, notes or absences.
- At most 60 requests per hour and key, and at most 120 requests in 10 minutes from the same IP address — counted together for all keys calling from that address. Above that, Zeitflex responds with 429 and states in the
Retry-Afterheader the seconds until the next permitted request. - Requests are recorded in the change log, combined: one line per key, IP address and month within 24 hours. If the same key calls from a different address or for a different month, that appears as a line of its own. Under Settings → API & webhooks you can see for each key when it was last used — that time applies to every single request.
Errors also come as JSON, with a sentence (fehler) and a fixed identifier (code) your program can rely on. The sentence is in German; with the header x-zeitflex-sprache: en it comes in English. Still branch on code, not on the sentence — the wording can change, the identifier does not:
| Status | code |
Meaning |
|---|---|---|
| 401 | schluessel_fehlt | No key in the Authorization header |
| 401 | schluessel_ungueltig | Unknown key — often copied incorrectly |
| 403 | schluessel_widerrufen | The key has been revoked |
| 403 | leserecht_fehlt | The key only has the Write permission |
| 403 | tarif_ohne_schnittstelle | The business is not (or no longer) on the Pro plan |
| 403 | betrieb_gesperrt | The business is scheduled for deletion |
| 400 | monat_ungueltig | monat is missing or not in the form YYYY-MM |
| 429 | gedrosselt | Too many requests — wait for Retry-After |
| 500 | interner_fehler | Zeitflex could not answer just now — try again in a few minutes |
If you want to bring data into Zeitflex, use the import under Import (see Importing data from Excel or another program). If you want to get data out, use the CSV export from the Starter plan or the DATEV export on the Pro plan (see Setting up the DATEV export).
What the interface deliberately does not do
- It does not provide location data. Whether coordinates are stored at all when clocking in/out is decided by the switch under Settings → GPS & privacy; without it, no coordinates are stored, whichever route is used to clock in/out. More in Location when clocking in/out.
- It sends no medical certificates and no reason texts of a reversal. Sickness and child sick appear only as the neutral type
ABWESENHEIT— but because the other types stay recognisable, a recipient can infer from it that it was none of them. - It does not send pay or remuneration data. Zeitflex only prepares payroll; the payroll itself is run by your payroll bureau or your tax adviser.
- It does not offer a connection to Google or Outlook calendars via OAuth. For the shift plan in your calendar, there is the per-person subscription under Settings → Calendar.
Still stuck?
Ask Support — or read on under Plans and prices and Location when clocking in/out.
Last checked on . We check every article against the application — if something no longer matches, that is a bug and not a difference of opinion.
Was this helpful?
Related articles
- Plans and pricesWhat Free, Starter and Pro include, how the price per active employee comes about, how monthly and annual payment differ and from when Enterprise applies.
- GPS when clocking in/out: what is stored, who sees it, when it is goneTwo switches, one snapshot when clocking in and out, six months — and no radius, no tracking. What Zeitflex does with the location and what it explicitly does not do.