Skip to content

Webhook endpoints ​

A webhook endpoint is a URL Zay sends events to. You can register up to 10. Managing them uses the same signed requests as the rest of the API; the deliveries themselves are signed differently — see Receive and verify webhooks.

New to webhooks? See How webhook delivery works. For what Zay sends, see the Event catalog.

EndpointOperation
POST /merchants/v1/webhook_endpointsRegister webhook endpoint
GET /merchants/v1/webhook_endpointsList webhook endpoints
GET /merchants/v1/webhook_endpoints/{id}Retrieve webhook endpoint
PATCH /merchants/v1/webhook_endpoints/{id}Update webhook endpoint
DELETE /merchants/v1/webhook_endpoints/{id}Delete webhook endpoint
POST /merchants/v1/webhook_endpoints/{id}/test_deliveryTrigger test delivery

The WebhookEndpoint object ​

json
{
  "webhook_endpoint": {
    "id": "11111111-1111-7000-8000-000000000001",
    "url": "https://example.com/zay-webhooks",
    "description": "Production endpoint",
    "status": "enabled",
    "created_at": "2025-12-05T12:05:49Z",
    "updated_at": "2025-12-05T12:05:49Z"
  }
}
FieldTypeDescription
idstring (uuid)
urlstring (uri)HTTPS URL Zay will POST webhook deliveries to.
descriptionstring | nullOptional free-form label.
statusstringEndpoint status. disabled is set either manually by the merchant or automatically when sustained delivery failures trip Zay's autodisable policy. One of enabled, disabled.
created_atstring (date-time)Creation date and time (ISO-8601)
updated_atstring (date-time)Creation date and time (ISO-8601)

New endpoints start enabled. Zay sets an endpoint to disabled itself when deliveries keep failing — see How webhook delivery works.

The signing secret ​

A signing_secret is generated when you register the endpoint. It is returned exactly once — in the response to Register webhook endpoint. Zay cannot show it to you again. Store it in your secret manager right away. If you lose it, delete the endpoint and register it again.

Secrets are prefixed with whsec_, matching the Standard Webhooks convention, so they are easy to identify and grep for in logs.

Register webhook endpoint ​

Register a new HTTPS URL to receive webhook deliveries. Returns the endpoint with its signing_secret — the only response that ever carries it.

POST /merchants/v1/webhook_endpoints

Request body

FieldTypeDescription
webhook_endpointrequiredobject
webhook_endpoint.urlrequiredstring (uri)
webhook_endpoint.descriptionstring | null

The url must use https and resolve to a public address — loopback, private (RFC 1918) and link-local addresses are rejected with 422.

Returns 201 Created with a wrapped webhook_endpoint that includes signing_secret.

Errors:

  • 401 — invalid signature.
  • 422 — the URL fails validation, or you already have 10 endpoints.
  • 429 — rate limit exceeded.

Examples ​

cURL ​

bash
curl -X POST "https://api-connect.zay.me/merchants/v1/webhook_endpoints" \
  -H "Content-Type: application/json" \
  -H "zay-api-key: $ZAY_API_KEY" \
  -H "zay-timestamp: $TS" \
  -H "zay-signature: $SIG" \
  -H 'Idempotency-Key: "d7c59ab2-3f12-4ad8-9b03-2a7b4f9c7e10"' \
  -d '{
    "webhook_endpoint": {
      "url": "https://merchant.example.com/zay-webhooks",
      "description": "Production endpoint"
    }
  }'

Python ​

python
body = json.dumps({
    "webhook_endpoint": {
        "url": "https://merchant.example.com/zay-webhooks",
        "description": "Production endpoint",
    }
})
url = "https://api-connect.zay.me/merchants/v1/webhook_endpoints"
headers = {
    "Content-Type": "application/json",
    "zay-api-key": ZAY_API_KEY,
    "Idempotency-Key": f'"{uuid.uuid4()}"',
    **merchant_headers("POST", url, ZAY_PRIVATE_KEY, body),
}
r = requests.post(url, headers=headers, data=body)
endpoint = r.json()["webhook_endpoint"]
signing_secret = endpoint["signing_secret"]  # store this immediately

Node.js ​

javascript
const url = "https://api-connect.zay.me/merchants/v1/webhook_endpoints";
const body = JSON.stringify({
  webhook_endpoint: {
    url: "https://merchant.example.com/zay-webhooks",
    description: "Production endpoint",
  },
});
const res = await fetch(url, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "zay-api-key": ZAY_API_KEY,
    "Idempotency-Key": `"${crypto.randomUUID()}"`,
    ...merchantHeaders("POST", url, ZAY_PRIVATE_KEY, body),
  },
  body,
});
const { webhook_endpoint: endpoint } = await res.json();
// endpoint.signing_secret — store immediately, you won't see it again

Response ​

json
{
  "webhook_endpoint": {
    "id": "11111111-1111-7000-8000-000000000001",
    "url": "https://example.com/zay-webhooks",
    "description": "Production endpoint",
    "status": "enabled",
    "created_at": "2025-12-05T12:05:49Z",
    "updated_at": "2025-12-05T12:05:49Z",
    "signing_secret": "whsec_DGVzdHRlc3R0ZXN0dGVzdHRlc3R0ZXN0dGVzdHRlc3Q="
  }
}

List webhook endpoints ​

Returns all webhook endpoints registered under your merchant account. There is no pagination — the cap is 10. signing_secret is never included.

GET /merchants/v1/webhook_endpoints

Errors:

  • 401 — invalid signature.
  • 429 — rate limit exceeded.

Example ​

bash
curl "https://api-connect.zay.me/merchants/v1/webhook_endpoints" \
  -H "zay-api-key: $ZAY_API_KEY" \
  -H "zay-timestamp: $TS" \
  -H "zay-signature: $SIG"

Response ​

json
{
  "webhook_endpoints": [
    {
      "id": "11111111-1111-7000-8000-000000000001",
      "url": "https://example.com/zay-webhooks",
      "description": "Production endpoint",
      "status": "enabled",
      "created_at": "2025-12-05T12:05:49Z",
      "updated_at": "2025-12-05T12:05:49Z"
    },
    {
      "id": "11111111-1111-7000-8000-000000000002",
      "url": "https://example.com/zay-webhooks/backup",
      "description": "Backup endpoint",
      "status": "enabled",
      "created_at": "2025-12-05T12:05:49Z",
      "updated_at": "2025-12-05T12:05:49Z"
    }
  ]
}

Retrieve webhook endpoint ​

Returns a single webhook endpoint by id. signing_secret is not included.

GET /merchants/v1/webhook_endpoints/{id}

Parameters

ParameterTypeDescription
idrequiredstring (uuid), pathWebhook endpoint ID

Errors:

  • 401 — invalid signature.
  • 404 — endpoint does not exist under this merchant.
  • 429 — rate limit exceeded.

Example ​

bash
curl "https://api-connect.zay.me/merchants/v1/webhook_endpoints/0192f5a4-7c48-4fe9-b957-738bd2e04adc" \
  -H "zay-api-key: $ZAY_API_KEY" \
  -H "zay-timestamp: $TS" \
  -H "zay-signature: $SIG"

Update webhook endpoint ​

Update one or more of url, description and status. signing_secret cannot be changed — to rotate a secret, delete the endpoint and register a new one.

PATCH /merchants/v1/webhook_endpoints/{id}

Parameters

ParameterTypeDescription
idrequiredstring (uuid), pathWebhook endpoint ID

Request body

FieldTypeDescription
webhook_endpointrequiredobject
webhook_endpoint.urlstring (uri)
webhook_endpoint.descriptionstring | null
webhook_endpoint.statusstringEndpoint status. disabled is set either manually by the merchant or automatically when sustained delivery failures trip Zay's autodisable policy. One of enabled, disabled.

Errors:

  • 401 — invalid signature.
  • 404 — endpoint not found.
  • 422 — the URL fails validation.
  • 429 — rate limit exceeded.

Example — re-enable a disabled endpoint ​

bash
curl -X PATCH "https://api-connect.zay.me/merchants/v1/webhook_endpoints/0192f5a4-7c48-4fe9-b957-738bd2e04adc" \
  -H "Content-Type: application/json" \
  -H "zay-api-key: $ZAY_API_KEY" \
  -H "zay-timestamp: $TS" \
  -H "zay-signature: $SIG" \
  -d '{ "webhook_endpoint": { "status": "enabled" } }'

Response ​

json
{
  "webhook_endpoint": {
    "id": "11111111-1111-7000-8000-000000000001",
    "url": "https://example.com/zay-webhooks",
    "description": "Migrated to v2 receiver",
    "status": "disabled",
    "created_at": "2025-12-05T12:05:49Z",
    "updated_at": "2025-12-05T12:05:49Z"
  }
}

Delete webhook endpoint ​

Delete a webhook endpoint. Deliveries still scheduled for it are dropped — there is no graceful drain.

DELETE /merchants/v1/webhook_endpoints/{id}

Parameters

ParameterTypeDescription
idrequiredstring (uuid), pathWebhook endpoint ID

Returns 204 No Content.

Errors:

  • 401 — invalid signature.
  • 404 — endpoint not found.
  • 429 — rate limit exceeded.

Example ​

bash
curl -X DELETE "https://api-connect.zay.me/merchants/v1/webhook_endpoints/0192f5a4-7c48-4fe9-b957-738bd2e04adc" \
  -H "zay-api-key: $ZAY_API_KEY" \
  -H "zay-timestamp: $TS" \
  -H "zay-signature: $SIG"

Trigger test delivery ​

Send a synthetic webhook_endpoint.test event to this endpoint through the full signed-delivery pipeline. Useful right after registering or re-enabling an endpoint, to confirm your receiver works before real events arrive.

POST /merchants/v1/webhook_endpoints/{id}/test_delivery

Parameters

ParameterTypeDescription
idrequiredstring (uuid), pathWebhook endpoint ID
  • The endpoint must be enabled. A test delivery to a disabled endpoint is accepted with 202 but never sent.
  • The 202 response carries the delivery's id. The same value arrives as the webhook-id header of the POST your receiver gets, so you can match the two in your logs.

Returns 202 Accepted with the queued webhook_delivery. A few seconds later your endpoint receives the event itself, in the format described in the Event catalog.

Errors:

  • 401 — invalid signature.
  • 404 — endpoint not found.
  • 429 — rate limit exceeded.

Example ​

bash
curl -X POST "https://api-connect.zay.me/merchants/v1/webhook_endpoints/0192f5a4-7c48-4fe9-b957-738bd2e04adc/test_delivery" \
  -H "zay-api-key: $ZAY_API_KEY" \
  -H "zay-timestamp: $TS" \
  -H "zay-signature: $SIG"

Response ​

json
{
  "webhook_delivery": {
    "id": "22222222-2222-7000-8000-000000000001",
    "type": "webhook_endpoint.test",
    "occurred_at": "2025-12-05T12:05:49Z",
    "data": {
      "webhook_endpoint_id": "11111111-1111-7000-8000-000000000001",
      "message": "This is a test event from Zay. If you received this, your endpoint is configured correctly."
    }
  }
}