Skip to navigation

Webhooks Overview

Webhooks let your application hear about changes in Run Payments without polling. We send them when a merchant’s boarding status changes in Run Partner and when a transaction is recorded or changes status. Each webhook is an HTTP POST request with a JSON body, sent to an endpoint you give us.

How Webhooks Work

When an event happens, such as a merchant being sent for signature or a transaction being declined, Run Payments sends a POST request to each endpoint subscribed to that event. The body is JSON: the event type, the Run merchant ID it concerns, and a payload with the details. Your endpoint responds with a 2xx status to confirm it received the webhook. If it doesn’t, the webhook is retried. See Retries.

Event Types

Boarding Events

See Boarding Webhooks for payloads and details.

  • merchant.new - Merchant created, or application moved back to new
  • merchant.sent_for_signature - Application sent for signature
  • merchant.signed - Application signed
  • merchant.in_underwriting - Application in underwriting
  • merchant.boarded - Merchant boarded
  • merchant.live - Merchant live
  • merchant.cancelled - Application cancelled
  • merchant.declined - Application declined
  • merchant.unknown - Merchant status changed to unknown
  • merchant.deleted - Merchant deleted from Run Partner
  • boarding.varcomplete - VAR details available for a merchant’s equipment

Transaction Events

See Transaction Webhooks for payloads and the transaction lifecycle.

  • transaction.entered - Transaction recorded, whatever its outcome
  • transaction.entered.<source> - Transaction recorded from one source, such as transaction.entered.hpp (sent with transaction.entered). See Sources.
  • transaction.batched - Transaction batched
  • transaction.captured - Transaction captured
  • transaction.processed - Transaction processed
  • transaction.decline - Transaction declined
  • transaction.reject - Transaction rejected
  • transaction.voided - Transaction voided
  • transaction.refund - Transaction fully refunded
  • transaction.partialrefund - Transaction partially refunded

Reporting Events

  • chargeback.entered - Chargeback recorded for a merchant
  • funding.entered - Funding information available for a merchant
  • statement.entered - Merchant statement available

See Reporting Event Payloads for payloads.

Request Body

Every webhook has the same top-level structure. Only payload differs by event.

{
"event_type": "transaction.entered",
"source_id": "104000",
"timestamp": "2026-10-15T14:49:28.181Z",
"payload": {
// Event-specific data
},
"metadata": {
"webhook_id": "wh_600000",
"attempt": 1,
"idempotency_key": "4f1c0a9e2b7d4c3e8a6b5d0f1e2c3b4a"
}
}
FieldTypeDescription
event_typeStringThe event that happened, such as merchant.signed or transaction.entered.
source_idStringThe Run merchant ID the event is about. This is the same for boarding and transaction events.
timestampStringWhen the webhook was created, in ISO 8601 format (UTC).
payloadObjectEvent-specific data. See the guide for each event type.
metadata.webhook_idStringID of this webhook.
metadata.attemptIntegerDelivery attempt number, starting at 1. See Retries.
metadata.idempotency_keyStringUnique key for the event. It stays the same on every delivery attempt. See Delivery.

Verifying Signatures

If your endpoint has a secret key, each request includes a signature header:

X-Webhook-Signature-256: sha256=<signature>

<signature> is the HMAC-SHA256 of the raw request body, keyed with your secret key, as lowercase hex. Your Integration Delivery Lead sets up the secret key, and can also set a different header name for your endpoint.

To verify a request:

  1. Read the raw request body as bytes, before parsing it as JSON. Parsing and re-serializing the body can change it and break the signature.
  2. Compute the HMAC-SHA256 of the raw body with your secret key and encode it as lowercase hex.
  3. Compare sha256= plus your result to the header value with a constant-time comparison.
  4. If they don’t match, reject the request.
Node.js
const crypto = require("crypto");
function isValidSignature(rawBody, signatureHeader, secretKey) {
const expected =
"sha256=" +
crypto.createHmac("sha256", secretKey).update(rawBody).digest("hex");
const received = Buffer.from(signatureHeader || "");
const computed = Buffer.from(expected);
return (
received.length === computed.length &&
crypto.timingSafeEqual(received, computed)
);
}
Python
import hashlib
import hmac
def is_valid_signature(raw_body: bytes, signature_header: str, secret_key: str) -> bool:
expected = "sha256=" + hmac.new(
secret_key.encode(), raw_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature_header or "")

The signature doesn’t include a timestamp, so a valid request could be captured and sent again. Use metadata.idempotency_key to ignore events you’ve already processed.

Delivery

  • At least once. The same event can be delivered more than once. metadata.idempotency_key stays the same on every delivery of an event, so store it and skip events you’ve already processed. Store the key only after your handler succeeds. If you store it first and the handler fails, you’ll skip the retry and lose the event.
  • No ordering guarantee. Events can arrive out of order, especially when a delivery is retried. Use the timestamps in the payload, not arrival order, to decide which event is most recent.
  • Respond quickly, then process. Verify the signature, store the event, and return a 2xx response. Do any slow work after you respond.
  • New fields and event types may be added. Ignore fields and event types you don’t recognize instead of rejecting the request.

Retries

How Run Payments handles your endpoint’s response:

Your responseResult
2xxDelivered. No retry.
408, 429, or any 5xxRetried.
No response (timeout or connection error)Retried.
Any other 4xx, such as 400 or 401Not retried. The event isn’t sent again.

If your handler can’t process a valid request, return a 5xx so it’s retried. Don’t return a 4xx.

Failed deliveries are retried, up to 8 attempts in total by default. If your endpoint keeps failing, deliveries to it pause for a few minutes.

Recovering Missed Events

If you miss an event, or delivery fails on every attempt, get the merchant’s current state from the Boarding API with GET /merchants/{merchant_id}.

Getting Started

  1. Configure endpoints: Send your webhook endpoint URLs, and the event types you want, to your Integration Delivery Lead.
  2. Verify signatures: Check the signature header on every request. See Verifying Signatures.
  3. Handle duplicates: Skip events whose metadata.idempotency_key you’ve already processed.
  4. Return the right status code: Return 2xx once the event is stored, and 5xx if you need it sent again. See Retries.