> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zipkit.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Receive automatic notifications when zip archives are ready

## Overview

Webhooks allow you to receive real-time notifications when your zip creation jobs complete. Instead of polling the status endpoint, ZipKit will automatically send an HTTP POST request to your specified URL when a zip finishes processing.

## Why Use Webhooks?

* ✅ **Real-time notifications** - Get instant updates when zips complete
* ✅ **Reduce API calls** - No need to poll the status endpoint repeatedly
* ✅ **Better user experience** - Respond immediately to completed zips
* ✅ **Reliable delivery** - Webhooks are sent as soon as the job completes

## Configuring Webhooks

Webhooks are configured through the ZipKit dashboard for your project.

### Step-by-Step Setup

<Steps>
  <Step title="Navigate to Webhooks">
    1. Log in to your ZipKit dashboard
    2. Select your project
    3. Go to the "Webhooks" section
    4. Click "Create Webhook" or "New Webhook"
  </Step>

  <Step title="Configure the webhook">
    **URL (Required)**
    Enter the full HTTPS URL where you want to receive webhook notifications.
    Example: `https://api.myapp.com/webhooks/zipkit`

    **Description (Optional)**
    Add a description to help identify this webhook's purpose.
    Example: "Production webhook for zip completion events"
  </Step>

  <Step title="Save and test">
    1. Click "Save" or "Create"
    2. Your webhook will now receive notifications for all zip jobs in this project
  </Step>
</Steps>

<Note>
  **HTTPS Required**: For security, webhook URLs must use HTTPS (not HTTP). This ensures webhook payloads are encrypted in transit.
</Note>

## Webhook Payload

When a zip completes (successfully or with failure), ZipKit sends a POST request to your webhook URL with a Stripe-style event payload:

### Event Structure

```json theme={null}
{
  "id": "evt_a1b2c3d4e5f6g7h8i9j0k1l2",
  "object": "event",
  "type": "zip.completed",
  "created": 1701234567,
  "data": {
    "object": {
      "id": "zip_abc123",
      "object": "zip",
      "status": "succeeded",
      "key": "reports/monthly-2025-11.zip",
      "bucket": {
        "service": "s3",
        "name": "my-destination-bucket"
      },
      "metadata": {
        "user_id": "user_123",
        "order_id": "order_456"
      },
      "checksum": "d8e8fca2dc0f896fd7cb4cb0031ba249",
      "size_bytes": 12345,
      "content_type": "application/zip"
    }
  }
}
```

### Failed Zip

When a zip fails, the payload includes an error object:

```json theme={null}
{
  "id": "evt_def456ghi789jkl012mno345",
  "object": "event",
  "type": "zip.completed",
  "created": 1701234567,
  "data": {
    "object": {
      "id": "zip_def456",
      "object": "zip",
      "status": "failed",
      "key": "reports/monthly-2025-11.zip",
      "bucket": {
        "service": "s3",
        "name": "my-destination-bucket"
      },
      "metadata": {
        "user_id": "user_123",
        "order_id": "order_456"
      }
    },
    "error": {
      "type": "bucket_error",
      "code": "upload_failed",
      "message": "Failed to upload zip to bucket"
    }
  }
}
```

## Event Fields

<ResponseField name="id" type="string">
  Unique event identifier with `evt_` prefix.
</ResponseField>

<ResponseField name="object" type="string">
  Always `"event"`.
</ResponseField>

<ResponseField name="type" type="string">
  The event type. Currently always `"zip.completed"`.
</ResponseField>

<ResponseField name="created" type="integer">
  Unix timestamp when the event was created.
</ResponseField>

<ResponseField name="data.object" type="object">
  The zip object that triggered the event.
</ResponseField>

<ResponseField name="data.error" type="object">
  Error details when the zip fails. Only present on failed zips. Contains `type`, `code`, and `message` fields.
</ResponseField>

## Zip Object Fields

<ResponseField name="data.object.id" type="string">
  Unique zip identifier with `zip_` prefix.
</ResponseField>

<ResponseField name="data.object.object" type="string">
  Always `"zip"`.
</ResponseField>

<ResponseField name="data.object.status" type="string">
  The final status of the zip job.

  **Possible values:**

  * `"succeeded"` - zip was created and uploaded successfully
  * `"failed"` - zip creation or upload failed
</ResponseField>

<ResponseField name="data.object.key" type="string">
  The storage key (filename/path) of the zip archive in the bucket.
</ResponseField>

<ResponseField name="data.object.bucket" type="object">
  Information about the destination bucket.
</ResponseField>

<ResponseField name="data.object.bucket.service" type="string">
  The cloud storage service (`"s3"` or `"r2"`).
</ResponseField>

<ResponseField name="data.object.bucket.name" type="string">
  The name of the bucket where the zip was stored (or attempted to be stored).
</ResponseField>

<ResponseField name="data.object.metadata" type="object">
  The custom metadata object that was provided when the zip was created. Contains any custom data you attached to track user IDs, order IDs, or other contextual information. Returns an empty object `{}` if no metadata was provided.
</ResponseField>

<ResponseField name="data.object.checksum" type="string">
  The ETag/checksum of the uploaded zip file. Only present for successful uploads.
</ResponseField>

<ResponseField name="data.object.size_bytes" type="integer">
  The size of the zip file in bytes. Only present for successful uploads.
</ResponseField>

<ResponseField name="data.object.content_type" type="string">
  The MIME type of the zip file. Typically `"application/zip"`. Only present for successful uploads.
</ResponseField>

## Implementing a Webhook Endpoint

Your webhook endpoint should:

1. **Accept POST requests** with JSON payloads
2. **Respond quickly** (within 5-10 seconds)
3. **Return a 2xx status code** to acknowledge receipt
4. **Verify the webhook source** (optional but recommended)

## Best Practices

<AccordionGroup>
  <Accordion title="Respond quickly" icon="bolt">
    Your endpoint should respond within 5-10 seconds. If you need to perform long-running tasks, process them asynchronously in a background job.

    ```python theme={null}
    @app.route('/webhooks/zipkit', methods=['POST'])
    def zipkit_webhook():
        payload = request.json

        # Queue for background processing
        celery.send_task('process_zip', args=[payload])

        # Respond immediately
        return jsonify({'received': True}), 200
    ```
  </Accordion>

  <Accordion title="Verify webhook authenticity" icon="shield-check">
    Since webhooks don't currently include authentication headers, verify requests by:

    * Restricting webhook endpoint access to ZipKit's IP addresses
    * Using HTTPS to ensure requests aren't intercepted
    * Validating the webhook payload structure and data types
    * Implementing request signing (future feature)
  </Accordion>

  <Accordion title="Handle retries gracefully" icon="arrows-rotate">
    Make your webhook endpoint idempotent. ZipKit may retry sending webhooks if your endpoint fails to respond. Use the event `id` or `data.object.id` to deduplicate events.

    ```python theme={null}
    # Check if we've already processed this event
    event_id = payload['id']
    if already_processed(event_id):
        return jsonify({'received': True}), 200

    # Process the webhook
    process_webhook(payload)
    mark_as_processed(event_id)
    ```
  </Accordion>

  <Accordion title="Log webhook receipts" icon="file-lines">
    Log all webhook receipts for debugging and auditing purposes.

    ```python theme={null}
    import logging

    zip_data = payload['data']['object']
    logger.info(f"Received {payload['type']} for zip {zip_data['id']}: {zip_data['status']}")
    ```
  </Accordion>

  <Accordion title="Return proper status codes" icon="check">
    * Return `200` or `204` for successful receipt
    * Return `401` for authentication failures
    * Return `500` only for actual server errors

    ZipKit considers 2xx responses as successful delivery.
  </Accordion>

  <Accordion title="Use HTTPS with valid certificates" icon="lock">
    Ensure your webhook endpoint uses HTTPS with a valid SSL certificate. Self-signed certificates may cause delivery failures.
  </Accordion>
</AccordionGroup>

## Testing Webhooks

### Local Development

For testing webhooks locally, use tools like:

* **[ngrok](https://ngrok.com/)** - Creates a public URL for your local server
* **[localtunnel](https://localtunnel.me/)** - Alternative tunneling service
* **[Webhook.site](https://webhook.site/)** - Inspect webhook payloads

**Example with ngrok:**

```bash theme={null}
# Start your local server
python app.py  # Running on port 5000

# In another terminal, create a tunnel
ngrok http 5000

# Use the ngrok HTTPS URL in ZipKit
# https://abc123.ngrok.io/webhooks/zipkit
```

### Production Testing

Create a test webhook that logs all received payloads to verify everything works correctly before going live.

## Managing Webhooks

### Viewing Webhooks

All configured webhooks are listed in your project's "Webhooks" section, showing:

* Webhook URL
* Description
* Creation date

**Security:** The Authorization header is never displayed after creation.

### Editing Webhooks

To update a webhook:

1. Go to "Webhooks" section
2. Click "Edit" next to the webhook
3. Update the URL, description, or authorization header
4. Save changes

### Deleting Webhooks

To remove a webhook:

1. Go to "Webhooks" section
2. Click "Delete" next to the webhook
3. Confirm deletion

<Note>
  Once deleted, you'll stop receiving notifications for zip completions. Any in-flight webhooks may still be delivered.
</Note>

## Troubleshooting

### Not receiving webhooks

**Possible causes:**

* Webhook URL is incorrect or unreachable
* Your server is blocking requests from ZipKit
* SSL certificate issues (expired or self-signed)
* Firewall blocking incoming requests

**Solutions:**

* Verify the webhook URL is correct and accessible
* Check server logs for incoming requests
* Ensure HTTPS is properly configured
* Test with webhook.site first to verify ZipKit is sending requests

### Receiving duplicate webhooks

This can happen if:

* Your endpoint took too long to respond
* Your endpoint returned an error status code
* Network issues caused ZipKit to retry

**Solution:** Make your webhook handler idempotent using the event `id` to deduplicate.

### Securing webhook endpoints

**Best practices:**

* Use HTTPS with valid SSL certificates
* Restrict access to your webhook endpoint by IP address
* Validate the structure and content of webhook payloads
* Log all webhook receipts for monitoring

## Security Considerations

<Warning>
  **Webhook Security**: Anyone who knows your webhook URL could potentially send fake webhook requests. Secure your webhook endpoint appropriately.
</Warning>

Best practices:

* ✅ Use HTTPS to encrypt webhook payloads in transit
* ✅ Restrict access to your webhook endpoint by IP address
* ✅ Validate the webhook payload structure and data types
* ✅ Use unique, hard-to-guess webhook URLs
* ✅ Log all webhook receipts for monitoring
* ✅ Implement idempotency to handle duplicate deliveries

## Multiple Webhooks

You can configure multiple webhook URLs in a single project for:

* Different endpoints for different environments
* Redundancy (send to multiple systems)
* Different services that need to be notified

All configured webhooks will receive notifications for every zip completion in the project.
