Subscribing to Webhooks
See Webhooks Overview first for delivery behavior, and the Event Catalog for the full list of event identifiers you can subscribe to.
1. Register an endpoint
authentication_token is optional. If set, Penny sends it as Authorization: Bearer <token> on each delivery; see Webhook Authorization. You can also pass filter_identifiers here; see Step 6.
2. Wait for provisioning
Creation is asynchronous: POST /notifications/webhooks/ returns 202 Accepted with "status": "pending_setup" and "updating": true while Penny provisions the delivery infrastructure. The webhook isn’t ready to verify yet. Poll GET /notifications/webhooks/{notification_endpoint_id} until status reaches pending_verification, then move on to Step 3.
status is one of nine values:
Both verification.reason (why setup or verification failed) and suspension.reason (why Penny suspended the webhook) are fields on the GET /notifications/webhooks/{notification_endpoint_id} response.
3. Verify the endpoint
A newly created webhook doesn’t deliver events until it’s verified. Verification confirms that you control the URL you registered.
POST /notifications/webhooks/{notification_endpoint_id}/verification sends a challenge to your URL. The challenge arrives like any other delivery, with "type": "endpoint.verification.pending" and "category": "verification"; its data.token is the verification code. Submit that code back with PATCH on the same path:
Only a verified, active webhook receives deliveries. Your receiver must accept the challenge request before verification completes, so handle endpoint.verification.pending before you check for subscribed event types.
4. Configure request signing (optional)
Webhook creation does not generate a signing secret. After provisioning finishes, create one:
Save the secret from this response. Webhook responses never return it, including with reveal_sensitive=true; has_signing_secret indicates whether signing is configured. Creating a second secret fails. To replace an existing secret, use POST /notifications/webhooks/{notification_endpoint_id}/signing_secret/rotate, which returns the same response shape. Rotation immediately changes the key used for new attempts.
Once a secret exists, each request carries Penny-Webhook-Timestamp and Penny-Webhook-Signature headers. Verify them using the entire secret string, including its prefix, as described in Verify Webhook Signatures.
5. Adjust which events it receives
Add or remove event identifiers without recreating the webhook:
6. Scope to specific objects (optional)
filter_identifiers narrows delivery to specific object IDs — for example, only transactions on one account, rather than every transaction on the business:
7. Inspect delivery history
GET /notifications/webhooks/events lists your business’s events. The identifier query parameter is optional; pass an event type to return only events of that type. Delivery status lives on each delivery, not on the event, so list an event’s deliveries to see whether it reached your endpoint:
GET /notifications/webhooks/events/{notification_event_id} returns a single event, including the exact payload that was delivered. GET /notifications/webhooks/{notification_endpoint_id}/deliveries lists deliveries to one endpoint. See Delivery status for the delivery fields and Delivery behavior for the retry schedule.
Pausing or archiving a webhook
PATCH .../deactivate pauses deliveries and keeps the configuration; resume with PATCH .../activate. DELETE /notifications/webhooks/{notification_endpoint_id} archives the webhook and returns 202 Accepted. An archived webhook moves to archived status and no longer receives deliveries; create a new webhook if you need the URL again.
Managing the authentication token
authentication_token is write-only. Supply your own token when creating the webhook, or replace it with PATCH /notifications/webhooks/{notification_endpoint_id}. Omit the field to keep the existing token; send "authentication_token": null to remove it. Changing or removing the token requires ownership verification again.
Webhook responses expose has_authentication_token instead of the token. The token is never returned, including with reveal_sensitive=true or in history responses. Store your token before sending it to Penny; no token-retrieval endpoint is provided.