Skip to main content
Webhooks push HTTP notifications to your server when key events happen: balance changes, redemptions, tier transitions, and more. Instead of polling the API for changes, you register a URL and Scrip delivers signed payloads the moment each event occurs.

Creating an Endpoint

Register a URL and specify which events you want to receive:
The response includes a secret starting with whsec_. Store it immediately. It cannot be retrieved later. You’ll use it to verify signatures.
Use "enabled_events": ["*"] to subscribe to all event types.
URLs must use HTTPS with a publicly resolvable hostname. Private IPs, localhost, .local, and .internal domains are rejected.

Event Types

Redemption lifecycle events

Authorization emits redemption.pending. Capture emits redemption.completed; failure and timeout emit redemption.failed; and cancellation emits redemption.cancelled. Pending-redemption resolution payloads include status. Failure and cancellation include failure_reason when supplied, while an automatic timeout uses failure_reason: "expired". Instant completion keeps the create-time payload shape.
Successful capture emits no balance.* event. If you maintain a balance mirror from webhooks, reconcile it with participant balance and statement reads so the authorization’s balance.held value does not remain in HELD.

Reward status events

reward.status_changed fires when a reward’s status changes between DRAFT, ACTIVE, OUT_OF_STOCK, and ARCHIVED. PATCH /v1/programs/{programId}/rewards/{rewardId} emits it when status changes. Same-status writes and non-status field updates do not. Creating a reward does not emit it; use the create response. For UNIT_BASED rewards with max_total, authorization and instant redemption can move ACTIVE to OUT_OF_STOCK. Fail, cancel, timeout, and reversal can move OUT_OF_STOCK back to ACTIVE. Under async fulfillment that cycle can repeat. Treat OUT_OF_STOCK as live inventory state, not a terminal catalog state. See Rewards catalog.

Payload Format

Every delivery sends a JSON envelope:
For the data object each event type delivers, see the event payloads reference.

Signature Verification

Every delivery includes a Scrip-Signature header so you can verify it came from Scrip and wasn’t tampered with.

Header Format

Verification Steps

1

Extract components

Parse the t and v1 values from the Scrip-Signature header.
2

Construct signed payload

Concatenate the timestamp, a literal dot, and the raw request body: {t}.{raw_body}
3

Compute expected signature

Calculate HMAC-SHA256(your_endpoint_secret, signed_payload) and hex-encode the result.
4

Compare signatures

Use constant-time comparison. Reject the request if they don’t match.
5

Check timestamp

Reject if abs(now - t) exceeds your tolerance. We recommend 5 minutes.

Example (Python)

Example (Node.js)

Retry Policy

If your endpoint has a retryable failure such as a 5xx response, network error, or timeout, Scrip retries with exponential backoff: After 8 attempts (~10.5 hours), the delivery is marked FAILED. You can manually resend any terminal delivery to send the event again.

Response Handling

Return a 2xx quickly (within 30 seconds). Process the payload asynchronously if your handler needs more time. The worker enforces a 30-second timeout per delivery attempt.

Delivery Error Codes

Failed attempts include a stable error_code alongside the human-readable last_error. Use error_code for alerts and dashboards.

Managing Endpoints

Disable and Re-enable

Temporarily stop deliveries without deleting the endpoint:
Disabling an endpoint immediately fails queued or in-flight deliveries for that endpoint with error_code: "endpoint_disabled". Set status back to ACTIVE to receive future matching events. Events that occurred while the endpoint was disabled are not retroactively delivered. If you disabled an endpoint temporarily and still want to deliver specific failed events, re-enable the endpoint and resend the terminal delivery records.

Rotate Secret

If a secret is compromised, rotate it immediately:
The old secret is invalidated immediately. Update your verification code with the new secret before any in-flight deliveries arrive.

Delete

Deleting an endpoint archives it. It stops receiving deliveries and is removed from list results:

Endpoint Health

Scrip monitors delivery health per endpoint. A bad destination can be paused temporarily, then auto-disabled if it keeps failing or stops draining its backlog. Use delivery stats to see whether an endpoint is healthy, paused, disabled, or building a backlog:
Each row includes the 24-hour delivery rollup (success_24h, fail_24h, success_rate, degraded) plus current backlog and block fields: pending_count, oldest_pending_at, circuit_broken_until, rate_limited_until, blocked_reason, and blocked_until. To re-enable an endpoint after resolving the underlying issue:
HTTP 429 responses do not count toward the failure-rate breaker. However, if rate limiting creates a sustained, non-draining backlog, Scrip can auto-disable the endpoint with error_code: "endpoint_backlog_exceeded". Re-enable the endpoint and resend any failed deliveries you still need.

Debugging Deliveries

List Deliveries for an Endpoint

Inspect a Delivery

The detail endpoint includes last_response_status, last_response_body (truncated to 4 KB), last_error, error_code, and resend_seq:

Resend a Delivery

Resend issues a new delivery of the same event to the same endpoint: a fresh delivery with its own id and an incremented resend_seq. The source delivery is left untouched, so you keep a complete history of every attempt.
The response is the newly created delivery (status PENDING), which you can poll like any other. Use resend to replay a webhook after fixing a bug in your handler, or to confirm your endpoint deduplicates correctly. Only terminal deliveries can be resent, and the endpoint must be active:

Best Practices

Delivery Guarantees

Webhook events are created atomically with their domain operations. If the underlying transaction rolls back, no webhook is emitted. Retried domain operations (like event reprocessing) do not produce duplicate webhooks. Delivery is at-least-once: a single event may be delivered more than once if your endpoint returns a 2xx but the acknowledgment is lost in transit. Design your handler to be idempotent using the envelope’s id field to detect duplicates.