Skip to main content
A webhook endpoint is a URL SouthPay posts events to, plus the list of events it wants and the secret used to sign them. This page covers managing those endpoints. For the payload shape, signature verification and retry behaviour, see the webhooks guide. Paths are relative to https://api.southpay.io/api/v2.

Authentication and scope

Reads require webhooks:read, writes require webhooks:write. Publishable keys are rejected.

Identifiers

Endpoint and delivery ids are bare UUIDs. There is no we_ or del_ prefix. No resource in this API uses prefixed ids.

List endpoints

GET /webhook_endpoints. Scope webhooks:read. Returns a bare array, newest first. No envelope, no pagination. Soft-deleted endpoints are excluded.
string
Filter by active, disabled or error.
delivery_success_rate is a percentage to one decimal over non-test deliveries, or null when there have been none. It appears on this view only.

Create an endpoint

POST /webhook_endpoints. Scope webhooks:write. Idempotency keys are not used here. All fields go inside a required webhook_endpoint object.
string
required
Where deliveries are posted. Must resolve to a public address. SouthPay rejects private, loopback and link-local hosts at delivery time.
string[]
required
Event names to receive. Must be a subset of the event catalogue. Omitting it on a non-platform endpoint yields an empty array, which fails validation.
string
Free text for your own reference.
string
Marks this as an e-commerce plugin integration: woocommerce, prestashop, shopify, magento or custom. Changes create behaviour. See platform integrations. Cannot be changed after create.
string
The storefront URL the plugin is installed on. Cannot be changed by PATCH.
object
Arbitrary key-value pairs stored on the endpoint. Not sent in deliveries.
Wildcards are not supported. "*" and "payment_intent.*" both fail validation. Subscription matching is literal array containment. To receive everything you must enumerate all 23 event names.
signing_secret is returned in full only by create and rotate. Store it now. Every other response gives you signing_secret_preview, the first 12 characters, which cannot verify a signature. Losing the secret means rotating it, which breaks in-flight deliveries.

Response fields

string
required
Bare UUID.
string
required
Delivery destination.
string
required
active, disabled (you turned it off) or error (SouthPay turned it off after 10 failed attempts on a delivery).
string[]
required
Exactly the events this endpoint receives.
string
Full plaintext secret, whsec_ plus 64 hex characters. Present on create and rotate only.
string
required
First 12 characters plus ..., for identifying which secret is in use.
string | null
The plugin platform, or null for a hand-registered endpoint.
boolean
required
True when platform is set. Platform integrations are limited to one per store per mode.
string
required
Stored as "2026-04-14". It is informational. No payload builder reads it, so changing endpoints does not change payload shape.
string | null
When the endpoint last confirmed a delivery.
number | null
Percentage to one decimal. Present on the list view only.

Retrieve an endpoint

GET /webhook_endpoints/{id}. Scope webhooks:read. Adds the last 25 deliveries and the full event catalogue. Note this view does not include delivery_success_rate.
Response 200
object[]
Last 25 in the current mode, newest first.
string[]
Every subscribable event name, as a convenience for building a settings UI.

Update an endpoint

PATCH /webhook_endpoints/{id}. Scope webhooks:write. Accepts url, description, subscribed_events and metadata only. platform and platform_store_url are fixed at create time and are silently ignored here. Returns 200 with the endpoint, without a signing_secret.

Enable and disable

PATCH /webhook_endpoints/{id}/enable sets status to active and clears the disabled timestamp. PATCH /webhook_endpoints/{id}/disable stops deliveries. Both take no body and return 200 with the endpoint.
Deliveries queued while an endpoint is not active are dropped without being rescheduled. Re-enabling does not replay them. Use redelivery or the payment events endpoint to backfill.

Delete an endpoint

DELETE /webhook_endpoints/{id}. Scope webhooks:write. Soft delete. Returns 204 with no body. The endpoint stops receiving deliveries and disappears from the list.

Rotate the signing secret

POST /webhook_endpoints/{id}/rotate_secret. Scope webhooks:write. No body. Returns 200 with the endpoint including the new full signing_secret.
Rotation is an immediate hard cutover. There is no overlap window and no second valid secret. Deliveries already signed with the old secret will fail verification at your end. Deploy the new secret before you rotate, or accept the retries.

Send a test delivery

POST /webhook_endpoints/{id}/send_test. Scope webhooks:write.
string
Event to simulate. Defaults to the endpoint’s first subscribed event, falling back to payment_intent.completed. Must be a valid event name.
Response 200
The response confirms queueing, not delivery. Poll GET /webhook_endpoints/{id} and look for the delivery_id in recent_deliveries. Test deliveries are excluded from delivery_success_rate. An unknown event_type returns 422 {"error": "Unknown event type"}.

Redeliver an event

POST /webhook_endpoints/{id}/redeliver. Scope webhooks:write.
string
required
The delivery to send again. Accepted in the JSON body or as a query parameter.
Returns 200 {"message": "Redelivery queued"}. A delivery that is not retryable returns 422 {"error": "Delivery is not retryable"}.
This endpoint is currently broken: every call returns 500 {"error": {"message": "Internal server error", "code": null}}. Until it is fixed, recover missed events with the payment events endpoint.

Platform integrations

Sending platform on create changes the semantics. Only one platform integration can exist per store per mode, so re-running a plugin’s connect flow does not fail. It adopts the existing endpoint:
  • The response is 200, not 201 and not 409.
  • Any of url, description, platform_store_url, metadata and subscribed_events you re-sent are applied. Omitted fields keep their current values, and empty subscribed_events falls back to the platform’s default set.
  • The endpoint is re-enabled if it was disabled.
  • The signing secret is always rotated. The new secret is in the response; the old one stops verifying immediately.
Plan for this in a plugin: treat a 200 from create exactly like a 201, and always persist the returned signing_secret. A 409 {"error": "An active woocommerce integration already exists for this store"} is still reachable if two registrations race the database index.

Event catalogue

23 events are subscribable.
The last five pass validation and can be subscribed to, but no code path ever emits them. If you are waiting on payout.confirmed, you are waiting forever. Poll GET /payouts instead. They remain listed here so existing subscribers can recognize why nothing arrives.
payment_intent.canceled is the opposite case: it is emitted when an agent cancels an intent, but it is not in the catalogue, so no endpoint can subscribe to it and it is dropped. Detect agent cancellations by polling for status: "expired" with state_reason: "canceled_by_agent".

Errors

Three envelopes appear on these endpoints.
Last modified on August 21, 2026