Skip to content
Markets open

API reference

Webhooks

Endpoints we POST signed events to, their delivery history and resends.

10 operationsBase URL https://packetexchange.io/api/v1Postman collection

List your webhook endpoints

GET/api/v1/account/webhooks

Access
API key. Scoped keys need account:read.
Rate limit
100 requests per second (the default)

Secrets are redacted to their last 4 characters.

Response 200

FieldTypeDescription
datarequiredobject[]-
idrequireddata[].idstring (uuid)-
urlrequireddata[].urlstring (uri)-
secretLast4requireddata[].secretLast4stringLast 4 characters of the signing secret, to tell secrets apart
eventsrequireddata[].eventsstring[]-One of call.completed, call.ringing, call.answered, call.gathered, sms.sent, sms.dlr, sms.delivered, sms.failed, campaign.started, campaign.completed, topup.confirmed, balance.low and 24 more
isActiverequireddata[].isActivebooleanFalse when you disabled it, or after sustained delivery failures
failureCountrequireddata[].failureCountintegerConsecutive failed deliveries; resets on success
lastDeliveryAtrequireddata[].lastDeliveryAtstring (date-time) | nullISO-8601 timestamp (UTC)
createdAtrequireddata[].createdAtstring (date-time)ISO-8601 timestamp (UTC)
updatedAtrequireddata[].updatedAtstring (date-time)ISO-8601 timestamp (UTC)

Errors

  • 400VALIDATION_ERROR, INVALID_INPUT or BAD_REQUEST. For VALIDATION_ERROR, error.details is an array of { path, message }.
  • 401UNAUTHORIZED: missing, invalid, expired or revoked credential.
  • 403FORBIDDEN: not allowed - a scoped key lacks the scope, or the endpoint is session-only.
  • 429RATE_LIMITED: slow down and retry after the Retry-After seconds.
  • 500INTERNAL_ERROR: unexpected failure. Quote X-Request-Id to support.

Create a webhook endpoint

POST/api/v1/account/webhooks

Access
API key. Scoped keys need webhooks:write.
Rate limit
100 requests per second (the default)

The signing secret is returned ONCE in secret. URLs must be https and resolve to a public address. Works from a dashboard session, or with an API key that was created with the webhooks:write permission. A full-access key does not include webhooks:write: it has to be chosen explicitly, so keys issued before this permission existed cannot manage endpoints.

Each delivery is a POST of { event, data, timestamp } with headers X-Webhook-Event, X-Webhook-Id (the delivery id), X-PX-Timestamp (Unix seconds at send time) and two signatures: X-PX-Signature: v1=<hex HMAC-SHA256 of "<timestamp>.<raw body>"> (verify this one, and reject timestamps more than 5 minutes old to stop replays) and the legacy X-Webhook-Signature: sha256=<hex HMAC-SHA256 of the raw body>, kept during the transition.

Request body (application/json)

FieldTypeDescription
urlrequiredstring (uri)-
eventsrequiredstring[]-One of call.completed, call.ringing, call.answered, call.gathered, sms.sent, sms.dlr, sms.delivered, sms.failed, campaign.started, campaign.completed, topup.confirmed, balance.low and 24 more

Response 201

FieldTypeDescription
datarequiredobject-
idrequireddata.idstring (uuid)-
urlrequireddata.urlstring (uri)-
secretLast4requireddata.secretLast4stringLast 4 characters of the signing secret, to tell secrets apart
eventsrequireddata.eventsstring[]-One of call.completed, call.ringing, call.answered, call.gathered, sms.sent, sms.dlr, sms.delivered, sms.failed, campaign.started, campaign.completed, topup.confirmed, balance.low and 24 more
isActiverequireddata.isActivebooleanFalse when you disabled it, or after sustained delivery failures
failureCountrequireddata.failureCountintegerConsecutive failed deliveries; resets on success
lastDeliveryAtrequireddata.lastDeliveryAtstring (date-time) | nullISO-8601 timestamp (UTC)
createdAtrequireddata.createdAtstring (date-time)ISO-8601 timestamp (UTC)
updatedAtrequireddata.updatedAtstring (date-time)ISO-8601 timestamp (UTC)
secretrequireddata.secretstringThe signing secret. Returned ONCE; store it now.

Errors

  • 400VALIDATION_ERROR, INVALID_INPUT or BAD_REQUEST. For VALIDATION_ERROR, error.details is an array of { path, message }.
  • 401UNAUTHORIZED: missing, invalid, expired or revoked credential.
  • 403FORBIDDEN: not allowed - a scoped key lacks the scope, or the endpoint is session-only.
  • 409CONFLICT (or a code-specific 409): the change clashes with existing state.
  • 429RATE_LIMITED: slow down and retry after the Retry-After seconds.
  • 500INTERNAL_ERROR: unexpected failure. Quote X-Request-Id to support.

Update a webhook endpoint

PATCH/api/v1/account/webhooks/{id}

Access
API key. Scoped keys need webhooks:write.
Rate limit
100 requests per second (the default)

Change the URL, the subscribed events or isActive. The signing secret and delivery history are kept. Works from a dashboard session, or with an API key that was created with the webhooks:write permission. A full-access key does not include webhooks:write: it has to be chosen explicitly, so keys issued before this permission existed cannot manage endpoints.

Parameters

NameInTypeDescription
idrequiredpathstring-

Request body (application/json)

FieldTypeDescription
urlstring (uri)-
eventsstring[]-One of call.completed, call.ringing, call.answered, call.gathered, sms.sent, sms.dlr, sms.delivered, sms.failed, campaign.started, campaign.completed, topup.confirmed, balance.low and 24 more
isActiveboolean-

Response 200

FieldTypeDescription
datarequiredobjectSame fields as Webhook, shown earlier on this page.

Errors

  • 400VALIDATION_ERROR, INVALID_INPUT or BAD_REQUEST. For VALIDATION_ERROR, error.details is an array of { path, message }.
  • 401UNAUTHORIZED: missing, invalid, expired or revoked credential.
  • 403FORBIDDEN: not allowed - a scoped key lacks the scope, or the endpoint is session-only.
  • 404NOT_FOUND: no such resource on your account.
  • 409CONFLICT (or a code-specific 409): the change clashes with existing state.
  • 429RATE_LIMITED: slow down and retry after the Retry-After seconds.
  • 500INTERNAL_ERROR: unexpected failure. Quote X-Request-Id to support.

Delete a webhook endpoint

DELETE/api/v1/account/webhooks/{id}

Access
API key. Scoped keys need webhooks:write.
Rate limit
100 requests per second (the default)

Removes the endpoint, its secret and its delivery history. Events stop immediately. Works from a dashboard session, or with an API key that was created with the webhooks:write permission. A full-access key does not include webhooks:write: it has to be chosen explicitly, so keys issued before this permission existed cannot manage endpoints.

Parameters

NameInTypeDescription
idrequiredpathstring-

Response 200

FieldTypeDescription
datarequiredobject-
idrequireddata.idstring (uuid)-
deletedrequireddata.deletedboolean-One of true

Errors

  • 400VALIDATION_ERROR, INVALID_INPUT or BAD_REQUEST. For VALIDATION_ERROR, error.details is an array of { path, message }.
  • 401UNAUTHORIZED: missing, invalid, expired or revoked credential.
  • 403FORBIDDEN: not allowed - a scoped key lacks the scope, or the endpoint is session-only.
  • 404NOT_FOUND: no such resource on your account.
  • 409CONFLICT (or a code-specific 409): the change clashes with existing state.
  • 429RATE_LIMITED: slow down and retry after the Retry-After seconds.
  • 500INTERNAL_ERROR: unexpected failure. Quote X-Request-Id to support.

Rotate a webhook signing secret

POST/api/v1/account/webhooks/{id}/rotate-secret

Access
API key. Scoped keys need webhooks:write.
Rate limit
100 requests per second (the default)

Issues a new secret, returned once. Deliveries are signed with it from this moment (including pending retries); there is no overlap window, so update your receiver at the same time. Works from a dashboard session, or with an API key that was created with the webhooks:write permission. A full-access key does not include webhooks:write: it has to be chosen explicitly, so keys issued before this permission existed cannot manage endpoints.

Parameters

NameInTypeDescription
idrequiredpathstring-

Response 200

FieldTypeDescription
datarequiredobject-
idrequireddata.idstring (uuid)-
urlrequireddata.urlstring (uri)-
secretLast4requireddata.secretLast4stringLast 4 characters of the signing secret, to tell secrets apart
eventsrequireddata.eventsstring[]-One of call.completed, call.ringing, call.answered, call.gathered, sms.sent, sms.dlr, sms.delivered, sms.failed, campaign.started, campaign.completed, topup.confirmed, balance.low and 24 more
isActiverequireddata.isActivebooleanFalse when you disabled it, or after sustained delivery failures
failureCountrequireddata.failureCountintegerConsecutive failed deliveries; resets on success
lastDeliveryAtrequireddata.lastDeliveryAtstring (date-time) | nullISO-8601 timestamp (UTC)
createdAtrequireddata.createdAtstring (date-time)ISO-8601 timestamp (UTC)
updatedAtrequireddata.updatedAtstring (date-time)ISO-8601 timestamp (UTC)
secretrequireddata.secretstringThe signing secret. Returned ONCE; store it now.
messagerequireddata.messagestring-

Errors

  • 400VALIDATION_ERROR, INVALID_INPUT or BAD_REQUEST. For VALIDATION_ERROR, error.details is an array of { path, message }.
  • 401UNAUTHORIZED: missing, invalid, expired or revoked credential.
  • 403FORBIDDEN: not allowed - a scoped key lacks the scope, or the endpoint is session-only.
  • 404NOT_FOUND: no such resource on your account.
  • 409CONFLICT (or a code-specific 409): the change clashes with existing state.
  • 429RATE_LIMITED: slow down and retry after the Retry-After seconds.
  • 500INTERNAL_ERROR: unexpected failure. Quote X-Request-Id to support.

Send a test ping to a webhook endpoint

POST/api/v1/account/webhooks/{id}/test

Access
API key. Scoped keys need account:write.
Rate limit
100 requests per second (the default)

Queues a ping delivery, signed like any other, usually delivered within 15 seconds.

Parameters

NameInTypeDescription
idrequiredpathstring-

Response 202

FieldTypeDescription
datarequiredobject-
idrequireddata.idstring (uuid)-
statusrequireddata.statusstring-One of pending, sending, delivered, failed
eventrequireddata.eventstring-One of ping

Errors

  • 400VALIDATION_ERROR, INVALID_INPUT or BAD_REQUEST. For VALIDATION_ERROR, error.details is an array of { path, message }.
  • 401UNAUTHORIZED: missing, invalid, expired or revoked credential.
  • 403FORBIDDEN: not allowed - a scoped key lacks the scope, or the endpoint is session-only.
  • 404NOT_FOUND: no such resource on your account.
  • 409CONFLICT (or a code-specific 409): the change clashes with existing state.
  • 429RATE_LIMITED: slow down and retry after the Retry-After seconds.
  • 500INTERNAL_ERROR: unexpected failure. Quote X-Request-Id to support.

List deliveries for one webhook endpoint

GET/api/v1/account/webhooks/{id}/deliveries

Access
API key. Scoped keys need account:read.
Rate limit
100 requests per second (the default)

Parameters

NameInTypeDescription
idrequiredpathstring-
cursorquerystring (uuid)The nextCursor from the previous page
limitqueryinteger-Default 20

Response 200

FieldTypeDescription
datarequiredobject[]-
idrequireddata[].idstring (uuid)Also sent as the X-Webhook-Id header. Automatic retries reuse it (de-duplicate on it); a manual resend is a new delivery with a new id.
webhookIddata[].webhookIdstring (uuid)Present on the account-wide list
urldata[].urlstringThe endpoint URL, on the account-wide list
eventrequireddata[].eventstring-
statusrequireddata[].statusstring-One of pending, sending, delivered, failed
httpStatusrequireddata[].httpStatusinteger | nullResponse code your server returned on the last attempt
attemptsrequireddata[].attemptsintegerAttempts so far (max 5, with attempts^2-minute backoff)
lastErrorrequireddata[].lastErrorstring | null-
nextRetryAtrequireddata[].nextRetryAtstring (date-time) | nullISO-8601 timestamp (UTC)
createdAtrequireddata[].createdAtstring (date-time)ISO-8601 timestamp (UTC)
deliveredAtrequireddata[].deliveredAtstring (date-time) | nullISO-8601 timestamp (UTC)
nextCursorrequiredstring | nullPass as cursor to fetch the next page; null on the last page.
hasMorerequiredbooleanTrue when another page exists.

Errors

  • 400VALIDATION_ERROR, INVALID_INPUT or BAD_REQUEST. For VALIDATION_ERROR, error.details is an array of { path, message }.
  • 401UNAUTHORIZED: missing, invalid, expired or revoked credential.
  • 403FORBIDDEN: not allowed - a scoped key lacks the scope, or the endpoint is session-only.
  • 404NOT_FOUND: no such resource on your account.
  • 429RATE_LIMITED: slow down and retry after the Retry-After seconds.
  • 500INTERNAL_ERROR: unexpected failure. Quote X-Request-Id to support.

List webhook deliveries across all your endpoints

GET/api/v1/account/webhooks/deliveries

Access
API key. Scoped keys need account:read.
Rate limit
100 requests per second (the default)

Newest first. Filter by endpoint, status or event. Includes the endpoint URL on each row.

Parameters

NameInTypeDescription
cursorquerystring (uuid)The nextCursor from the previous page
limitqueryinteger-Default 20
webhookIdquerystring (uuid)Only deliveries to this endpoint
statusquerystring-One of pending, sending, delivered, failed
eventquerystringOnly this event name, e.g. call.completed

Response 200

FieldTypeDescription
datarequiredobject[]Same fields as WebhookDelivery, shown earlier on this page.
nextCursorrequiredstring | nullPass as cursor to fetch the next page; null on the last page.
hasMorerequiredbooleanTrue when another page exists.

Errors

  • 400VALIDATION_ERROR, INVALID_INPUT or BAD_REQUEST. For VALIDATION_ERROR, error.details is an array of { path, message }.
  • 401UNAUTHORIZED: missing, invalid, expired or revoked credential.
  • 403FORBIDDEN: not allowed - a scoped key lacks the scope, or the endpoint is session-only.
  • 429RATE_LIMITED: slow down and retry after the Retry-After seconds.
  • 500INTERNAL_ERROR: unexpected failure. Quote X-Request-Id to support.

Get one webhook delivery, with its payload

GET/api/v1/account/webhooks/deliveries/{deliveryId}

Access
API key. Scoped keys need account:read.
Rate limit
100 requests per second (the default)

Parameters

NameInTypeDescription
deliveryIdrequiredpathstring-

Response 200

FieldTypeDescription
datarequiredobject-
idrequireddata.idstring (uuid)Also sent as the X-Webhook-Id header. Automatic retries reuse it (de-duplicate on it); a manual resend is a new delivery with a new id.
webhookIddata.webhookIdstring (uuid)Present on the account-wide list
urldata.urlstringThe endpoint URL, on the account-wide list
eventrequireddata.eventstring-
statusrequireddata.statusstring-One of pending, sending, delivered, failed
httpStatusrequireddata.httpStatusinteger | nullResponse code your server returned on the last attempt
attemptsrequireddata.attemptsintegerAttempts so far (max 5, with attempts^2-minute backoff)
lastErrorrequireddata.lastErrorstring | null-
nextRetryAtrequireddata.nextRetryAtstring (date-time) | nullISO-8601 timestamp (UTC)
createdAtrequireddata.createdAtstring (date-time)ISO-8601 timestamp (UTC)
deliveredAtrequireddata.deliveredAtstring (date-time) | nullISO-8601 timestamp (UTC)
payloadrequireddata.payloadobjectThe exact JSON body that was (or will be) POSTed

Errors

  • 400VALIDATION_ERROR, INVALID_INPUT or BAD_REQUEST. For VALIDATION_ERROR, error.details is an array of { path, message }.
  • 401UNAUTHORIZED: missing, invalid, expired or revoked credential.
  • 403FORBIDDEN: not allowed - a scoped key lacks the scope, or the endpoint is session-only.
  • 404NOT_FOUND: no such resource on your account.
  • 429RATE_LIMITED: slow down and retry after the Retry-After seconds.
  • 500INTERNAL_ERROR: unexpected failure. Quote X-Request-Id to support.

Resend a webhook delivery

POST/api/v1/account/webhooks/deliveries/{deliveryId}/resend

Access
API key. Scoped keys need account:write.
Rate limit
30 requests per minute

Queues a NEW delivery with the same event and payload to the same endpoint, signed with a fresh timestamp. The original delivery is left as it was, so the history shows both. The new delivery has its own id (X-Webhook-Id); the payload is byte-identical, so de-duplicate on your own event data if you need exactly-once processing. The endpoint must be active.

Parameters

NameInTypeDescription
deliveryIdrequiredpathstring-

Response 202

FieldTypeDescription
datarequiredobjectSame fields as WebhookDelivery, shown earlier on this page.

Errors

  • 400VALIDATION_ERROR, INVALID_INPUT or BAD_REQUEST. For VALIDATION_ERROR, error.details is an array of { path, message }.
  • 401UNAUTHORIZED: missing, invalid, expired or revoked credential.
  • 403FORBIDDEN: not allowed - a scoped key lacks the scope, or the endpoint is session-only.
  • 404NOT_FOUND: no such resource on your account.
  • 409CONFLICT (or a code-specific 409): the change clashes with existing state.
  • 429RATE_LIMITED: slow down and retry after the Retry-After seconds.
  • 500INTERNAL_ERROR: unexpected failure. Quote X-Request-Id to support.