Choose an endpoint
Create an endpoint
Register a URL and specify which events you want to receive. Subscribe only to the types you handle."enabled_events": ["*"] receives every type.
secret starting with whsec_. Store it immediately. It cannot be retrieved later. You’ll use it to verify signatures.
Event types
Every event type is one of two kinds.Movement and lifecycle events
A movement event announces one journal entry:balance.*, transfer.completed, and program.*. Every journal action has exactly one movement event. A participant or group balance changes only through movement events that name it, as the subject, as the counterparty, or as a transfer source or recipient. A handler that applies those events in order reconstructs every participant and group balance. Program wallet balances cannot be reconstructed from webhooks: issuance from a prefunded wallet, voids that return value to it, and settlement differences post to the wallet without an event naming it. Reconcile wallets from Get program balance or Get program statement.
A lifecycle event announces a resource changing state: redemption.*, fulfillment_account.*, reward.*, participant.*, and event.*. It carries the resource id, its status where the resource has one, and the ids of related resources. It never carries a journal entry id; the matching movement event does.
Movement events
Lifecycle events
Authorization emits
redemption.pending and balance.held. Capture emits redemption.completed and balance.redeemed with bucket: "HELD". An instant redemption emits redemption.completed and balance.redeemed with bucket: "AVAILABLE". Failure, timeout, and cancellation emit redemption.failed or redemption.cancelled, plus balance.released when live reserved value was released. If you maintain a participant or group balance mirror from webhooks, apply the movement events; the lifecycle events tell you why each one happened.
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.
Gift-card events
Use gift-card events to update your application when an order finishes. Onredemption.fulfillment_succeeded, retrieve a claim link for the redemption and make it available to the participant. On redemption.fulfillment_failed, show the failed outcome and inspect failure_reason.
If you receive fulfillment_account.funds_low, add funds to the account in the dashboard. Scrip retries the order automatically. This alert is sent once per fulfillment, even if several attempts encounter a low balance.
fulfillment_account.status_changed tells you when the account’s status changes, for example when it becomes ACTIVE, is disabled, or needs to be reconnected. Check status_reason and act on the account in the dashboard.
These events include the redemption and program IDs so you can find the purchase in your application. They do not include a claim link. See Gift cards for handling orders that finish after the initial request.
For the data object each type delivers, see Event payloads.
Read the payload
Every delivery sends a JSON envelope:Versioning
Adding a field to a payload never changesapi_version, so ignore fields you do not recognize. A change that renames, removes, or retypes a field gets a new api_version date. The value is fixed when the event is created. A retried delivery keeps the value it was created with, so a delivery labeled with an older date carries that older contract’s shapes.
Verify a signature
Every delivery includes aScrip-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.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. Scrip times out each attempt after 30 seconds.
Delivery error codes
Failed attempts include a stableerror_code alongside the human-readable last_error. Use error_code for alerts and dashboards.
Manage an endpoint
Disable and re-enable
Temporarily stop deliveries without deleting the endpoint: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:Delete
Deleting an endpoint archives it. It stops receiving deliveries and is removed from list results:Check 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:
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:
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.
Recover a delivery
List deliveries for an endpoint
Inspect a delivery
The detail endpoint includeslast_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 ownid and an incremented resend_seq. The source delivery is left untouched, so you keep a complete history of every attempt.
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.
If a request fails
Delivery guarantees
Webhook events are created with their domain operations. If the underlying transaction rolls back, no webhook is emitted. Each webhook event is recorded under an idempotency key, so a retried operation such as event reprocessing never produces a second event. The key istype:resource_id, where the resource is the one the event leads with: the journal entry for a movement event, the redemption for a redemption status event, the reversal for redemption.reversed and event.reversed, the fulfillment for the two fulfillment events and fulfillment_account.funds_low, the event for event.completed and event.failed, and the participant for participant.created. Three event types can fire more than once for the same resource, so their keys also name the transition.
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.
Event processing
When events complete or fail, and what to poll.
Redemption lifecycle
redemption.pending, capture, and timeout events.Testing
Replay deliveries against a development endpoint.