resources
playbook 5 min · Apr 28, 2026

Onboarding that starts on signature

Most teams treat the signed contract as the end of a workflow. It is a much better beginning — if your handler is built for at-least-once delivery and out-of-order events.

The contract is signed. In most companies that fact then sits in an inbox until somebody notices it and starts doing things: create the account, open the channel, schedule the kickoff, release the first payment.

The signature is a clean, unambiguous, machine-readable trigger. Treat it as the first step of onboarding rather than the last step of sales and the gap between signed and started goes from days to seconds.

The event you want

Usign emits contract lifecycle events to any HTTPS endpoint you register:

EventFires when
contract.createdA draft is created — nothing sent yet
contract.sentDispatched; signing emails are out
contract.viewedA signer opened it
contract.signed_by_signerOne signer signed — fires per signer
contract.fully_signedEvery signer has signed
contract.declinedA signer declined
contract.voidedCancelled with a reason
contract.expiredPassed its expiry unsigned

For onboarding you want contract.fully_signed. The common bug is wiring to contract.signed_by_signer instead and kicking off onboarding when the first of two parties signs.

Verify before you act

Every delivery carries an X-Usign-Signature header in the Stripe-style format:

X-Usign-Signature: t=<unix_timestamp>,v1=<hex_signature>

To verify: split out t and v1, compute HMAC-SHA256(secret, t + "." + raw_body) with your endpoint's signing secret, and compare against v1 in constant time. Reject anything where now - t exceeds about five minutes and replay attacks stop being interesting.

Compute the HMAC over the raw body, before any JSON parsing. Frameworks that helpfully parse and re-serialise the body will change the bytes and break verification in ways that are miserable to debug.

Correlate to something of yours

The payload identifies the contract. Your onboarding system cares about a deal, a creator, or an account — so the mapping has to have been established earlier.

That is what external_reference is for. Set it at creation to a stable handle from your own system, and the handler becomes a lookup instead of a guess. Retrofitting a correlation key onto contracts already in flight is not an option, so decide before the batch goes out.

Two properties your handler needs

Idempotent

Delivery is at-least-once. A timeout or an ambiguous 5xx means the same event arrives again. Process by the event id in the envelope, record what you have handled, and ignore repeats — or your best customer gets two welcome emails and two invoices.

Order-independent

There is no ordering guarantee, especially across event types. contract.viewed can land before contract.sent. Write handlers that reconcile against current state rather than assuming a sequence.

Failed deliveries retry with exponential backoff for up to 24 hours, so a brief outage on your side is survivable — provided the handler is safe to run twice.

What to actually trigger

The signature is the moment you have the most consent and the most attention. Reasonable things to hang off it:

  • Provision the account and email credentials while the tab is still open
  • Open the shared channel with the right people already in it
  • Release the first payment milestone, or queue it for approval
  • Write the signed PDF into the record that owns the relationship
  • Start the clock on whatever the contract actually obliges you to do

None of this is novel automation. It is just moving the trigger from a human noticed an email to the counterparty signed, which is the same event, minus the delay and minus the chance that nobody notices at all.

Wire up a webhook

Event payloads, signature verification, and the retry schedule.