Native integrations
Google Sheets, Google Calendar, Notion and HubSpot, connected by OAuth from the dashboard and driven by the same event catalog as webhooks.
The model
A connection is one OAuth grant to one external account. A binding says “when order.created happens, run sheets.append_rowwith this configuration”. Each event that matches a binding produces one sync job, and the job history is queryable.
curl https://api.zapyflow.com.br/api/integrations \
-H "Authorization: Bearer $ZAPYFLOW_API_KEY"Google — one connection, two capabilities
Sheets and Calendar appear as two capabilities of a singleGoogle connection, not two connections. Revoking any token of a Google grant revokes the whole grant, so two separate connections would mean “disconnect Sheets” silently killing your Calendar sync. Adding the second capability triggers incremental consent.
For Sheets we request drive.file, not full spreadsheet access: it reaches only the files you explicitly pick, which is enough to append a row.
Retries do not duplicate your data
This is the property that decides whether an integration is usable, and each provider gets the strongest mechanism it actually offers — not a generic one.
| Action | How a retry is made safe |
|---|---|
calendar.upsert_event | Google accepts a client-supplied event id, so we derive one from the appointment. A duplicate insert returns 409 and we switch to patch. Truly idempotent creation, with no extra read. |
hubspot.upsert_contact / upsert_deal | We create the custom properties zapyflow_lead_id and zapyflow_order_id with hasUniqueValue and use HubSpot's native batch upsert. This also matches WhatsApp leads that have a phone and no email — the majority here. |
sheets.append_row | Sheets has no upsert, so the sheet needs a zapyflow_event_id column. The happy path is a single append; we only scan that column on an ambiguous retry (a failure after the request left). |
notion.create_page | Same shape: a ZapyFlow ID property on the database, queried only on an ambiguous retry. |
Updates find the record their creation made: the reference is keyed by the entity (order, lead, appointment), not by the event. Without that, order.updated would open a new deal in your pipeline every time.
Failure handling
Sync jobs retry on the same schedule as webhooks — six attempts over ~31 hours — but only for failures that time can fix. A revoked grant or a deleted target is terminal.
| Status | Meaning |
|---|---|
| SUCCEEDED | Written to the provider. externalRef holds the id on their side. |
| SKIPPED | Deliberately not written — for example the sheet is missing the id column. The reason is on the job. |
| FAILED | Terminal failure. Fix the cause and retry the job. |
| EXHAUSTED | Six attempts used up. |
After 20 consecutive failures a binding is switched off — not the connection. A broken sheet must not take down a Calendar sync that works on the same account.
Revoked connections
When a user revokes access at the provider, nothing tells us. A daily probe detects it within 24 hours and moves the connection to REVOKED, which surfaces a reconnect prompt in the dashboard and pauses its bindings instead of burning retries.
Disconnecting
DELETE /api/integrations/{installationId} deletes our stored credential and disables the bindings. The response says whether we could also revoke at the provider — Notion has no revocation endpoint, and we report revokedAtProvider: false rather than implying an access cut we did not make.