SETSimple Easy Transactions
SET DocsAPI reference and complianceUpdated 2026-11-08
Browse: Overview

Webhooks and events

Every state change is an event. Events are listed in order at GET /v1/events (Direct Deposit partners: GET /api/partner/events), and when SET has a webhook URL for your account each one is also POSTed to it, signed.

Event types

ObjectTypes
Customercustomer.updated
Bank accountbank_account.created
Cash-Out Addresscash_out_address.created, cash_out_address.updated
Virtual accountvirtual_account.created, virtual_account.deactivated, virtual_account.activated
Depositdeposit.<state> (for example deposit.funds_received, deposit.payment_processed), deposit.delivered, deposit.chain_mismatch, deposit.chain_failed
Cash-Out paymentcash_out_payment.<state> (for example cash_out_payment.payment_processed, cash_out_payment.returned)
Conversionconversion_address.created, conversion_payment.<state>, conversion_payment.delivered, .chain_mismatch, .chain_failed
Walletwallet.created, wallet.activity, wallet_withdrawal.created, wallet_withdrawal.<state>
Invoice (Merchant API)invoice.created, invoice.<state> (paid, short, over), invoice.voided, payment.matched, payment.unmatched, journal_entry.created
Accountreturn_addresses.updated, ping (test)
Direct Deposit partnerpayout.<state>, payable_payment.<state>, payee.direct_allowed, payee.direct_revoked, ping, listed at GET /api/partner/events
{ "id": "evt_…", "object": "event", "type": "deposit.payment_processed", "livemode": true, "createdAt": "…", "data": { … } }

Verifying the signature

SET-Signature: t=1793072643,v1=5f2c…

v1 is the hex HMAC-SHA256 of "<t>.<raw body>" keyed with your webhook secret (GET /v1/webhook_secret). Verify before parsing:

const [t, v1] = header.split(",").map((part) => part.split("=")[1]);
const expected = hmacSha256Hex(secret, `${t}.${rawBody}`);
if (!timingSafeEqual(expected, v1) || Math.abs(Date.now() / 1000 - Number(t)) > 300) reject();

Reject a timestamp more than five minutes old, compare in constant time, and use the raw bytes of the body.

Delivery

  • A 2xx answer is delivered. Anything else is retried: after 1 minute, doubling, at most a day apart, 15 tries.
  • Delivery is at least once. Deduplicate on the event id.
  • Order is not guaranteed across retries; use createdAt and the object's forward-only state.
  • GET /v1/events/{id} shows the delivery record; POST /v1/events/{id}/redeliver sends one again; POST /v1/webhook_test sends a signed ping.
  • Without a webhook, poll GET /v1/events?after=<last id>.

What an event proves

deposit.payment_processed means the provider sent the stablecoin; deposit.delivered means SET read it on chain at the destination. cash_out_payment.payment_processed (paidToBank: true) means the provider paid the bank. Treat only these as outcomes; never a redirect or a status your front end saw.