Getting Started
Webhooks & Events
Events
An event is emitted anytime an object is updated on the database. An event is broken down into two sections, topic and context. The topic generally aligns with the object or API that the event was triggered on and the context explains what happened.
For example, shipment.delivered or fulfillment.created are events where shipment and fulfillment are the topics of the events, and the context is that the shipment was delivered and the fulfillment was created. You'll find more details about all events that each topic can emit in their API sections.
Event Model
id string
The unique ID of the event prefixed with event_.
object "event"
The object type.
type string
The event type in dot notation (e.g., shipment.created, fulfillment.updated).
topic string
The topic of the event, corresponding to the resource type (e.g., shipment, fulfillment, contact).
data object|null
The resource data at the time the event was triggered.
message object|null
The message associated with the event, if any.
resource_id string|null
The ID of the resource that triggered the event.
location_id string|null
The ID of the location associated with the event, if applicable.
status string
The processing status of the event.
steps array
An array of processing steps that were performed for this event. Each step tracks the outcome of an individual operation (e.g., webhook delivery setup, log creation, push notifications, SMS/email notifications). Steps provide visibility into exactly which operations succeeded or failed.
Show Object
batch_id string|null
The ID of the batch associated with the event, if applicable.
initial_webhooks array
The webhook IDs that were assigned when the event was created.
pending_webhooks array
The webhook IDs that have not yet been attempted.
attempted_webhooks array
The webhook IDs that have had at least one delivery attempt.
pending_workflows array
The workflow IDs that are pending execution for this event.
created_at integer
Time in epoch seconds when this event was created.
updated_at integer
Time in epoch seconds when this event was last updated.
Example Response
List Events
Retrieve all events for your organization. The response includes a data array and pagination object.
Filtering
You can filter events using the following query parameters:
Retrieve Event
Retrieve a single event using its id.
Webhooks
You may want to listen to these events to trigger actions downstream outside of the platform instead. In the Developer Center section of the dashboard, you can set up webhooks and subscribe to whatever events you want to listen to. You can create a maximum of 10 webhooks per organization. Our webhooks provide a generous 5000ms wait time per attempt, after which the attempt will be marked as failed if a 2XX status code is not received.
Webhook Authentication
When creating the webhook, you are able to specify a custom header of your choice to be sent with the request. You may specify both the key and value. Certain header keys are blocked for security reasons, including standard transport headers (Host, Content-Type, Transfer-Encoding), credential headers (Cookie, Authorization), and proxy headers (X-Forwarded-For, X-Real-IP).
While we encrypt all information in our databases, we believe that the best practice here is to provide a hashed or encrypted value to us, and to implement some extra authentication on your end.
Webhook Payload
When a subscribed event occurs, we send a
Discord and Slack webhook URLs receive simplified payloads formatted for their respective platforms.
Webhook Model
id string
The unique ID of the webhook prefixed with hook_.
object "webhook"
The object type.
url string
The URL that webhook events will be sent to.
events array
The list of events this webhook is subscribed to, in dot notation (e.g., shipment.created).
status string
The current status of the webhook. One of enabled, disabled, or suspended.
retry_mode string
The retry strategy for failed deliveries. One of fast, balanced, or slow. Defaults to balanced.
supersede boolean
When enabled, skips retries for stale events if a newer event exists for the same resource. Defaults to false.
max_concurrency integer
A target for how many deliveries we send to this endpoint at the same time. Between 1 and 250, defaults to 100. Deliveries beyond it wait in the queue and are sent as earlier ones complete, in order per resource. It is a target rather than a hard cap: we count deliveries already in flight, and brief bursts slightly above your value are possible.
header object
A custom header sent with each webhook request.
Show Object
disabled_at integer|null
Time in epoch seconds when the webhook was disabled or suspended. null if the webhook is active.
enable_at integer|null
Time in epoch seconds when a suspended webhook will be automatically re-enabled. null if not suspended.
stats object
Delivery statistics for the webhook, updated daily.
Show Object
checksum string
A checksum of the webhook configuration, used for change detection.
created_at integer
Time in epoch seconds when this resource was created.
updated_at integer
Time in epoch seconds when this resource was last updated.
Example Response
Create Webhook
Creates a new webhook. On creation, a test
2XX status code within 5000ms, the request will be rejected. Request Body
url string (required)
The URL to receive webhook events. Must be a valid http:// or https:// URL. Localhost, raw IP addresses (IPv4/IPv6), and cloud metadata endpoints are blocked.
events array (required)
An array of event types to subscribe to. Must contain at least one event.
header object
A custom header to include with every webhook request.
retry_mode string
The retry strategy. One of fast, balanced (default), or slow.
supersede boolean
Enable supersede mode. Defaults to false.
max_concurrency integer
A target for simultaneous deliveries to your endpoint. Between 1 and 250, defaults to 100. Lower it if your endpoint cannot keep up. Raising it lets us send more at once when there is a backlog for you; it does not reserve capacity or give you priority over other traffic.
Update Webhook
Updates an existing webhook. If the url or header changes, a test
disabled webhook with a valid URL, the webhook will be automatically re-enabled upon a successful test. All fields are optional.
Retrieve Webhook
Retrieve a single webhook using its id.
List Webhooks
Retrieve all webhooks for your organization. The response includes a data array and pagination object.
Filtering
You can filter webhooks using the following query parameters:
Delete Webhook
Delete a webhook using its id. This will also remove all pending and historical delivery records for the webhook.
Outgoing Webhook Model
An outgoing webhook represents a single delivery attempt (or series of attempts) for a specific event to a specific webhook endpoint. Each time an event is created, one outgoing webhook record is created per matching webhook subscription.
id string
The unique ID of the outgoing webhook.
object "outgoing_webhook"
The object type.
organization object
The organization this outgoing webhook belongs to.
Show Object
webhook object
The webhook this delivery is for.
Show Object
event object
The event being delivered.
Show Object
status string
The current delivery status. One of dropped, queued, processing, superseded, completed, failed, or timed_out.
attempt_count integer
The number of delivery attempts made so far (0-5).
attempts array
An array of delivery attempt records. Each attempt contains:
Show Object
retry_after_ms is the wait your endpoint asked for on this attempt, in milliseconds, after our 5 minute cap. It is set only when the attempt was rate limited with a 429 response and stated a wait - via Retry-After, or failing that RateLimit-Reset or X-RateLimit-Reset. It is the value we took as a FLOOR, not the delay actually applied: the real wait is that floor or our own retry delay, whichever is longer, plus up to 25% spread.
first_attempt_at integer|null
Time in epoch seconds of the first delivery attempt. null if no attempt has been made.
last_attempt_at integer|null
Time in epoch seconds of the most recent delivery attempt. null if no attempt has been made.
last_attempt_status_code integer|null
The HTTP status code from the most recent attempt. null if no attempt has been made or if the attempt was a network error.
last_attempt_duration float|null
The duration in milliseconds of the most recent attempt. null if no attempt has been made.
next_attempt_at integer|null
Time in epoch seconds when the next retry is scheduled. null if no retry is pending.
created_at integer
Time in epoch seconds when this record was created.
updated_at integer
Time in epoch seconds when this record was last updated.
Example Response
Data Retention
Outgoing webhook records are automatically purged based on their status. Records are cleaned up daily.
List Outgoing Webhooks
Retrieve all outgoing webhook delivery records for your organization. Use this to monitor webhook delivery health, debug failed deliveries, and inspect attempt history. The response includes a data array and pagination object.
Filtering
You can filter outgoing webhooks using the following query parameters:
Ordering
Use the order_by parameter to sort results. Supported values: created_at, next_attempt_at, last_attempt_at, first_attempt_at, attempt_count.
List Outgoing Webhooks by Event
Retrieve all outgoing webhook delivery records for a specific event. This is useful for inspecting which webhooks were notified for a particular event and what the delivery status is for each. This endpoint requires both events:read and webhooks:read scopes.
Retry Modes
When a webhook delivery fails (non-2XX response or timeout), the system will retry the delivery up to 4 additional times for a maximum of 5 total attempts. You can choose between three retry strategies when creating or updating a webhook.
The delays below are exact for an ordinary failure. A retry that was rate limited with a 429 is spread by up to 25% either side of the listed value instead, so a burst of rate-limited deliveries does not come back in lockstep - see Rate Limiting.
Fast Mode
Best for time-sensitive integrations where you want quick retries.
Total retry window: approximately 2 minutes. For a rate-limited delivery (see Rate limiting) the window can reach approximately 25 minutes.
Balanced Mode (default)
A middle ground, and the default for new webhooks. It gives a struggling endpoint room to recover without holding a delivery for the best part of an hour.
Total retry window: approximately 12 minutes, or up to approximately 29 minutes for a rate-limited delivery.
Slow Mode
Best for integrations where the receiving server may need more time to recover.
Total retry window: approximately 50 minutes. For a rate-limited delivery (see Rate limiting) the window can reach around 70 minutes.
Existing webhooks keep whichever mode they are already set to. Changing retry_mode affects future retries only; a delivery already waiting keeps the schedule it was given.
Rate Limiting
If your endpoint answers a delivery with 429 Too Many Requests, we treat it as a request to slow down rather than as a failure on your side:
- We wait as long as your endpoint asks, up to 5 minutes per retry. Tell us how long with
Retry-After- either a number of seconds or an HTTP date. If that header is absent or unreadable we also read, in order,RateLimit-Reset-After,X-RateLimit-Reset-After,RateLimit-ResetandX-RateLimit-Reset. Whatever we take from you is reported back on the attempt asretry_after_ms, and it acts as a floor: the actual wait is that value or our own retry delay, whichever is longer, plus the usual spread. Send the reset headers in seconds from now if you can - that is what the-Afterpair mean, and they cannot be misread. The two without the suffix are ambiguous in the wild, so we read a large value as a UNIX timestamp and a small one as a number of seconds. - The whole endpoint pauses, not just the one delivery. A stated wait applies to everything queued for that endpoint. Deliveries held back this way are not attempted at all, so they do not spend one of their 5 attempts waiting for you.
- Retries are spread out. When a burst of deliveries is rate limited at once, their retries are scattered rather than sent together, so they do not arrive as a second burst. The spread goes both ways, so an individual retry can land slightly sooner than the listed delay as well as later - never sooner than a wait you asked for.
- Rate limits do not suspend your webhook.
429responses are excluded from the hourly temporary-suspend check below, so a rate-limited endpoint keeps receiving deliveries at a reduced pace instead of being paused for an hour. - We reduce how much we send you, automatically. While your endpoint keeps returning
429, we lower the number of simultaneous deliveries to it, and raise it again once it stops. This needs no configuration from you. It works in terms of simultaneous deliveries rather than requests per second, so if your rate limit is measured per second it will reduce the pressure rather than land exactly on your limit - setmax_concurrencyon the webhook if you want a specific ceiling.
A 429 still consumes one of the delivery's 5 attempts: a delivery rate limited on every attempt ends as failed like any other.
All of the above applies to 429 only. If your gateway answers with 503 Service Unavailable instead - which is what nginx's limit_req returns by default - the delivery counts as an ordinary failure: we do not read your Retry-After, we do not slow down, and the responses do count toward the hourly suspension. If you rate limit us, answer with 429.
What the daily health checks do and do not catch. They count deliveries that finished - delivered, or out of attempts. A delivery that was rate limited a few times and then accepted counts as a success, so an endpoint that rate limits heavily but eventually accepts everything can look healthy. An endpoint where deliveries genuinely run out of attempts is still reported as degraded and can still be permanently disabled.
If you would rather set the limit yourself than have us discover it, use max_concurrency on the webhook - the automatic reduction applies beneath whatever value you set.
Lowering max_concurrency works even while your endpoint is rate limiting everything - a save that changes only the retry and delivery settings goes straight through. Three saves do send a live test request to your URL first, and a 429 to that request is treated as a failed validation and answered with a 400: creating a webhook, changing its URL or a custom header, and any save against a webhook that is currently disabled. If you need one of those while your endpoint is rate limiting us, either let it recover first or exempt our test request from your rate limit.
Supersede Mode
When supersede is enabled on a webhook, the system will skip retries for an event if a newer event already exists for the same resource and webhook combination. Instead of retrying, the older event is marked as superseded.
This is useful for high-frequency resources like shipment tracking, where the consumer only cares about the latest state. Retrying stale updates would waste the retry budget when a more recent update is already queued.
Ordering Guarantee
Webhooks for the same resource are delivered in chronological order. Only one delivery per resource and webhook combination can be in-flight at a time. If a webhook is processing an event for a given resource, no additional events for that same resource will be dispatched until the current delivery completes, fails all retries, or is abandoned.
A delivery is treated as abandoned if it stays in processing for more than 15 minutes without finishing, which releases the resource so later events are not blocked indefinitely. In that case the abandoned delivery moves to timed_out and the next event for the resource is dispatched.
Webhook Health Monitoring
PackageX automatically monitors the health of your webhooks and takes action to protect both your integration and our delivery infrastructure.
Temporary Suspend
If a webhook accumulates 50 or more failures with 0 successes within the last hour, it will be automatically suspended for 1 hour. Deliveries whose most recent attempt was rate limited with a 429 response do not count toward this - see Rate limiting. Delivery is paused for the suspension: events are still queued, no delivery attempts are made against the endpoint, and the queue is delivered in order once the webhook is re-enabled. No events are lost, and the pause means the suspension does not consume the retry budget of the events waiting behind it.
When a webhook is suspended, a webhook.suspended event is emitted and a notification email is sent. The webhook's enable_at field indicates when it will automatically resume. After the suspension period, the webhook is automatically re-enabled and queued events begin processing.
Permanent Disable
Webhook health is evaluated daily. The degraded warning and the permanent disable are independent checks:
- Degraded warning: If a webhook has a failure rate above 30% (success rate below 70%) with more than 100 failures in the last 24 hours, a warning email is sent to organization administrators and a
webhook.degradedevent is emitted. - Permanent disable: If a webhook has a failure rate above 30% with more than 500 failures in both the last 24-hour window and the previous 24-hour window, it is permanently disabled. A notification email is sent and a
webhook.disabledevent is emitted.
Re-enabling a Disabled Webhook
A disabled webhook can be re-enabled by updating it via the API. When you update a disabled webhook (for example, by correcting the URL), a test
enabled. While a webhook is disabled no delivery attempts are made, and its already-queued events are held for 48 hours so that re-enabling within that window resumes delivery of the backlog. Events still queued after 48 hours are moved to dropped and are not delivered. Re-enable promptly if the pending events matter to you.
Webhook Lifecycle Events
The webhook topic emits the following events, which can be listened to via other webhooks:
URL Validation
Webhook URLs must be valid http:// or https:// URLs. The following URL types are blocked for security:
localhost- Raw IPv4 addresses (e.g.,
192.168.1.1) - Raw IPv6 addresses
- Cloud metadata endpoints
When creating or updating a webhook, a test
2XX status code within 5000ms, the create or update request will be rejected with a 400 error. Integration Error Notifications
In addition to standard webhook-driven events, PackageX can send email notifications when integration errors exceed a configured threshold. These notifications apply to both outgoing integrations (e.g., NetSuite, webhooks you push data to) and incoming webhooks (data received from external systems).
When the error count within a configurable time window exceeds your organization's failure limit, an event is published and email notifications are sent to all users subscribed to the corresponding event. These events are internal notification events and are not dispatched to your webhook endpoints.
To configure these notifications, see the integration_notifications section of your Organization Settings.
Event Subscribers
Event subscribers are organization-managed email destinations that always receive a curated set of system-level operational alerts (webhook health and integration errors), independent of any user account or user notification setting. Use them to guarantee that an operations or compliance inbox is always notified - for example, an alerts@yourcompany.com address that should hear about a permanently disabled webhook regardless of who is logged in.
Subscriber emails are merged - de-duplicated - into the same emails these events already send to your organization's users, so a subscriber receives the identical message body.
Subscribable Events
Only these system-level event types can be subscribed to. Request the live list from GET /v1/events/types?type=system.
Lifecycle
A subscriber must confirm its address (and, depending on your org, be approved by an owner) before it starts receiving emails. Recipients cannot unsubscribe themselves - only the organization can remove a subscriber.
- Create the subscriber. It starts as
pending_verificationand a verification email is sent to the address. - Verify: the recipient clicks the link in that email, which confirms the address. If the subscriber was already approved at creation it becomes
active; otherwise it moves topending_approval. A subscriber is only auto-approved at creation when it is created by a user who is the organization's sole owner - a subscriber created with an API key is never auto-approved and always moves topending_approvalafter verification. - Approve (dual control): a
pending_approvalsubscriber must be approved by an organization owner. Approval is a user action available only to an organization owner who did not create the subscriber (use the dashboard, or a user-authenticated request). API keys cannot approve subscribers - an attempt returns403. Because of this, a subscriber created with an API key always needs a human owner to approve it before it goesactive. - A subscriber whose verification link is never confirmed expires to
verification_failedafter 3 days; an owner can resend a fresh link (capped at 3 verification emails per address).
Who can create and approve
Creation is open to any caller with the notifications:write scope (including API keys). Approval is an owner-only user action and is never available to API keys.
Event Subscriber Model
id string
The unique ID of the event subscriber prefixed with esub_.
object "event_subscriber"
The object type.
channel string
The delivery channel. Currently always email.
destination string
The email address that receives the subscribed events.
events array
The list of subscribed system-level event types, in dot notation.
status string
One of pending_verification, pending_approval, active, or verification_failed.
verified_at integer|null
Time in epoch seconds when the address was verified. null if not yet verified.
approved_by string|null
The ID of the user who approved the subscriber. null if not yet approved.
approved_at integer|null
Time in epoch seconds when the subscriber was approved. null if not yet approved.
created_by string|null
The ID of the user who created the subscriber.
created_at integer
Time in epoch seconds when this subscriber was created.
updated_at integer
Time in epoch seconds when this subscriber was last updated.
Example Response
List Subscribable Event Types
Returns the catalog of event types that can be subscribed to. Pass ?type=system to return only the system-level events listed above, or ?type=all (default) for the complete event catalog. The response is a paginated data array of dotted event-type strings.
Create Event Subscriber
Creates a subscriber in pending_verification and sends a verification email to the address. Requires the notifications:write scope. An organization may have at most 25 event subscribers.
Request Body
email string (required)
The email address to subscribe.
events array (required)
A non-empty array of system-level event types to subscribe to (see the table above).
Update Event Subscriber
Updates a subscriber. Requires the notifications:write scope. The destination address and channel are immutable (remove and re-add to change the address). The body supports:
events array
Replace the subscribed events. On an active subscriber this is restricted to an organization owner or the subscriber's original creator.
approve boolean
Approve a verified, pending_approval subscriber (dual control). This is an owner-only user action: it succeeds only for an organization owner who did not create the subscriber. It cannot be performed with an API key - an API-key request returns 403. Approval is normally done from the dashboard.
resend boolean
Resend the verification email (only valid while the subscriber is unverified; capped at 3 sends per address).
Retrieve Event Subscriber
Retrieve a single subscriber using its id. Requires the notifications:read scope.
List Event Subscribers
Retrieve all event subscribers for your organization. Requires the notifications:read scope. Supports filtering by status, channel, destination, events, created_at, and updated_at, and ordering by created_at, updated_at, status, or destination.
Delete Event Subscriber
Remove a subscriber using its id. Requires the notifications:write scope. This is the only way to stop delivery to an address - recipients cannot unsubscribe themselves.