# Guide to Zift Webhooks: Features and Best Practices

Zift webhooks (Zift calls them notifications) notify your application about billing and processing activity: a subscription is created, a payment is processed, a chargeback or return occurs. If you're building on Zift, notifications are how you react to these events without polling.

This guide covers how Zift notifications work, the acknowledgement handshake, why there's no signature to verify, and the best practices for production.

## What are Zift webhooks?

Zift notifications are HTTPS POSTs carrying a JSON body. There is no signing secret, no signature header, and no Basic or token auth on delivery. This is confirmed from the full rendered docs, not an absence of documentation. Authenticity relies only on HTTPS and endpoint-URL secrecy, so treat Zift's source as unverified passthrough and add your own controls. To acknowledge each delivery, your endpoint must return JSON `{"notificationId": <id>}`.

## Zift webhook features

| Feature | Details |
| --- | --- |
| Delivery | HTTPS POST, JSON body |
| Verification | None documented (no secret, no signature header, no token auth) |
| Acknowledgement | Return JSON `{"notificationId": <id>}` (int or string) |
| Retries | On non-ack: +5min, +15min, +60min, +24h, then status "Failed" (no further redelivery) |
| Payload | `notificationId`, `eventCode` (e.g. `billing.subscription-created`), `eventDate` (epoch-ms), `dataType`, `data{}` |
| Setup | Out-of-band: email Zift support your HTTPS endpoint and events (integrator/reseller level) |
| SDK | None |

## Common events

Zift has a naming quirk: the trigger names you subscribe to use tildes, while the `eventCode` in the payload uses dots. Map between them:

| Trigger name | Payload `eventCode` (example) |
| --- | --- |
| `subscription~create` | `billing.subscription-created` |
| `payment-option~create` | billing payment-option created |
| `allocation~create` | billing allocation created |
| `payment~process` | billing payment processed |
| `chargeback` | processing chargeback |
| `return` | processing return |
| `reversal` | processing reversal |
| `NOC` | processing NOC |

Subscribe with the tilde trigger names, but branch your handler on the dotted `eventCode` in the payload.

## Setting up Zift webhooks

Setup is out-of-band: email Zift support your HTTPS endpoint and the events you want. Notifications are configured at the integrator or reseller level, not per merchant. There's no self-serve dashboard and no secret to store, so keep your endpoint URL secret and consider IP allowlisting.

## Securing Zift webhooks

There's no signature to verify, so security rests on transport and process. Serve the endpoint over HTTPS only, keep the URL secret, and consider allowlisting Zift's source IPs. Acknowledge every delivery by echoing its `notificationId`, otherwise Zift retries and eventually marks the notification "Failed".

```javascript
app.post("/webhook", express.json(), (req, res) => {
  // No signature to verify. Authenticity rests on HTTPS + URL secrecy (+ optional IP allowlist).
  const { notificationId, eventCode } = req.body;

  // Acknowledge by echoing the notificationId, or Zift will retry and then mark it Failed
  res.json({ notificationId });

  // Branch on the dotted eventCode (payload), not the tilde trigger name (subscription)
  processQueue.add({ eventCode, body: req.body }); // async, dedupe on notificationId
});

```

The same handler in Python (FastAPI):

```python
from fastapi import FastAPI, Request

app = FastAPI()

@app.post("/webhook")
async def zift_webhook(request: Request):
    body = await request.json()
    # No signature to verify; acknowledge by echoing notificationId
    enqueue(body)  # async; branch on body["eventCode"], dedupe on notificationId
    return {"notificationId": body["notificationId"]}

```

## Zift webhook limitations and pain points

### There's no signature to verify

The Problem: Zift delivers with no signing secret, signature header, or token auth. You can't cryptographically prove a request came from Zift, so a leaked URL is enough for anyone to post events.

Why It Happens: Zift's notification delivery relies on HTTPS and URL secrecy.

Workarounds:

* Serve HTTPS only, keep the endpoint URL secret, allowlist Zift's source IPs, and validate the payload shape before acting.

How Hookdeck Can Help: Hookdeck can front the endpoint, apply IP allowlisting and its own controls, and give you a managed layer in front of an unverifiable source.

### Acknowledgement must echo the notificationId

The Problem: A plain 200 isn't enough. Zift treats a delivery as acknowledged only when you return `{"notificationId": <id>}`. Miss it and Zift retries, then marks the notification "Failed".

Why It Happens: Zift confirms delivery by matching the echoed id.

Workarounds:

* Return `{"notificationId": <id>}` with the exact id from the request.

How Hookdeck Can Help: Hookdeck can manage the acknowledgement contract at the edge, so a missed echo doesn't fail deliveries.

### Trigger names and eventCode differ

The Problem: You subscribe to tilde names like `subscription~create`, but the payload carries dotted codes like `billing.subscription-created`. Branching on the trigger name instead of the `eventCode` never matches.

Why It Happens: Zift uses different naming for subscription triggers and payload codes.

Workarounds:

* Subscribe with tilde names, but branch handlers on the dotted `eventCode`.

How Hookdeck Can Help: Hookdeck's filters route on the `eventCode` you actually receive, so the naming mismatch doesn't leak into your app.

### Retries end in a terminal Failed state

The Problem: On non-ack, Zift retries at +5m, +15m, +60m, +24h, then marks the notification "Failed" with no further redelivery. A prolonged outage means permanently lost events.

Why It Happens: Zift's retry schedule is finite.

Workarounds:

* Acknowledge fast and reliably, and reconcile via the API for anything that reached "Failed".

How Hookdeck Can Help: Hookdeck durably queues events and retries on its own schedule, so a downstream outage doesn't drop events at Zift's terminal state.

## Best practices

### Treat the source as unverified passthrough

There's no signature, so rely on HTTPS, URL secrecy, IP allowlisting, and payload-shape validation.

### Echo the notificationId to acknowledge

Return `{"notificationId": <id>}` with the exact id, or the delivery isn't acknowledged.

### Branch on the dotted eventCode

Subscribe with tilde trigger names, but handle events by the payload `eventCode`.

### Return the ack fast and process asynchronously

Acknowledge quickly and defer work to a queue; dedupe on `notificationId`. See [why to process webhooks asynchronously](/webhooks/guides/why-implement-asynchronous-processing-webhooks).

## Conclusion

Zift notifications have no documented signature verification, so authenticity rests on HTTPS and URL secrecy, treat the source as unverified passthrough. Acknowledge each delivery by echoing `{"notificationId": <id>}`, or Zift retries at +5m/+15m/+60m/+24h then marks it "Failed". Subscribe with tilde trigger names, branch on the dotted `eventCode`, and dedupe on `notificationId`.

[Hookdeck](https://hookdeck.com) fronts your endpoint, manages the acknowledgement, deduplicates, and durably queues every notification at the edge, so your app processes a reliable, unique stream.

[Get started with Hookdeck](https://dashboard.hookdeck.com/signup) for free and handle Zift webhooks reliably in minutes.