Skip to content

Webhooks

Webhooks let your server receive HTTP callbacks when a ZIP job finishes. Instead of polling the Jobs API, register an endpoint URL and Eazip will POST the result to you automatically.

  1. Log in to your Eazip dashboard
  2. Navigate to Webhooks in the sidebar
  3. Click Add Endpoint and enter your HTTPS endpoint URL
  4. After creation, click the endpoint to open the detail page and copy the Active Secret

You can view your signing secret at any time from the webhook detail page.

  • The endpoint URL must use HTTPS.
  • Your server must respond within 10 seconds with a 2xx status code.

A webhook is fired whenever a ZIP job reaches a terminal state:

EventTrigger
job.completedZIP file is ready for download
job.failedJob failed — all files could not be fetched, or a fail-fast error occurred

If you have multiple webhook endpoints registered, each one receives its own delivery independently.


Every webhook delivery is an HTTP POST with Content-Type: application/json. The body has the following structure:

{
"delivery_id": "d4e5f6a7-b8c9-4d2e-a1f3-9c8b7a6e5d4f",
"event": "job.completed",
"job_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "completed",
"url_count": 3,
"file_count": 3,
"errors": null,
"download_url": "https://api.eazip.io/download/eyJ...",
"metadata": { "order_id": "ord_123" },
"timestamp": "2025-01-21T10:00:45.000Z"
}
FieldTypeDescription
delivery_idstringUnique UUID for this webhook delivery. Use this for idempotency checks — the same delivery_id is sent on retries of the same delivery.
eventstring"job.completed" or "job.failed"
job_idstringUUID of the ZIP job
statusstringJob status (completed or failed)
url_countnumberNumber of URLs submitted in the job
file_countnumber | nullNumber of files actually packaged (null if the job failed before packaging)
errorsarray | nullArray of { url, error } objects for failed file fetches. null when there are no errors.
download_urlstring | nullSigned download URL for the ZIP. null when the job failed.
metadataobject | nullThe key/value pairs you passed when creating the job
timestampstringISO 8601 timestamp of when the event was generated
{
"delivery_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"event": "job.failed",
"job_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "failed",
"url_count": 2,
"file_count": null,
"errors": [
{ "url": "https://example.com/missing.pdf", "error": "HTTP 404" }
],
"download_url": null,
"metadata": null,
"timestamp": "2025-01-21T10:01:00.000Z"
}

Every delivery includes an X-Webhook-Signature header containing an HMAC-SHA256 signature. Always verify this signature before processing the payload to ensure it was sent by Eazip and has not been tampered with.

  1. Eazip computes HMAC-SHA256(request_body, your_signing_secret) and hex-encodes the result.
  2. The hex string is sent in the X-Webhook-Signature header.
  3. Your server computes the same HMAC over the raw request body using your stored secret and compares it.
import { createHmac, timingSafeEqual } from 'node:crypto';
function verifyWebhook(rawBody, signatureHeader, secret) {
const expected = createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
// During secret rotation, the header may contain two
// comma-separated signatures — check each one.
const signatures = signatureHeader.split(',');
return signatures.some((sig) => {
if (sig.length !== expected.length) return false;
return timingSafeEqual(
Buffer.from(sig, 'utf8'),
Buffer.from(expected, 'utf8'),
);
});
}

Over time you may need to rotate your signing secret — for example, after a team member leaves or as part of a regular security policy. Eazip supports zero-downtime rotation with a grace period.

  1. Open the webhook detail page in the dashboard and click Rotate Secret. Choose a grace period (1–24 hours) and click Rotate.
  2. Eazip generates a new Pending Secret. Both the Active Secret and the new Pending Secret are displayed on the detail page.
  3. During the grace period, every delivery includes two comma-separated signatures in X-Webhook-Signature — one computed with the active secret and one with the pending secret.
  4. Update your server to verify against the new secret.
  5. When the grace period expires, the pending secret automatically promotes to the active secret. From that point, only one signature is sent.

While a rotation is in progress, the Rotate Secret button shows “Rotation in progress” and is disabled. The remaining grace period is displayed as a badge next to Grace Period Remaining.

Rotate called Grace period expires
│ │
▼ ▼
─────┬────────────────────────────────┬──────────
│ Both signatures sent │ New secret only
│ (active + pending) │ (pending → active)
─────┴────────────────────────────────┴──────────

Only one rotation can be in progress at a time. Attempting to rotate while a pending secret exists returns 409 Conflict.


If your endpoint does not return a 2xx response (or the request times out after 10 seconds), Eazip will automatically retry the delivery.

Retries are processed on a short interval. Up to 5 attempts are made in total. After 5 failed attempts, the delivery is marked as permanently failed and no further retries are attempted.

  • Any HTTP response with a non-2xx status code (e.g. 500, 503, 429)
  • Connection timeout (no response within 10 seconds)
  • Connection error (endpoint unreachable)

Each delivery has one of these statuses:

StatusDescription
pendingDelivery in progress or scheduled for retry
successEndpoint returned a 2xx response
failedAll retry attempts exhausted, or endpoint was deactivated

Open a webhook’s detail page in the dashboard and select the Delivery History tab. Each delivery shows:

ColumnDescription
EventEvent type (e.g. job.completed)
Statuspending (yellow), success (green), or failed (red)
HTTP StatusThe HTTP status code returned by your endpoint (blank if no response was received)
AttemptsNumber of delivery attempts made so far
SentWhen the delivery was first created

The most recent 50 deliveries are shown.


Your endpoint may receive the same event more than once (e.g. due to retries after a network issue where the response was lost). Design your handler to be idempotent — use delivery_id as a deduplication key to avoid processing the same delivery twice. The delivery_id stays the same across retries of the same delivery. You can also use job_id if you only need one handler invocation per job regardless of how many endpoints you have registered.


  1. Always verify signatures — never trust incoming webhooks without checking the HMAC signature.
  2. Respond quickly — return a 200 immediately and process the payload asynchronously. If your handler takes too long, the delivery will time out and be retried.
  3. Handle duplicates — use delivery_id to deduplicate deliveries in case of retries (or job_id to deduplicate per job).
  4. Use HTTPS — webhook URLs must use HTTPS. This ensures the payload (including download_url) is encrypted in transit.
  5. Rotate secrets regularly — use the grace period mechanism to rotate secrets without downtime.
  6. Monitor deliveries — check delivery history to catch persistent failures early.