Reference

Build on the Lucra API.

Drag a sticker to move it, or use the arrow keys while focused.

Webhooks

Events Lucra sends you, and verifying them.

Lucra POSTs an event to your server when something changes, so you don't have to poll.

Set up

Add a webhook URL when you create a key, or have the key set its own with PATCH /v1/keys/current ({ "webhookUrl": "https://…" }; null stops events). Lucra shows a signing secret (whsec_…) once; setting a URL again issues a new one. GET /v1/keys/current reads the URL.

Who hears an event

A key hears events about its own account and, for a partner, the brands it reaches. A partner key also hears every event about a creator active on its roster (data.creator), wherever it happens: their applications, submissions, messages, retainers, payments, connections and payout setup. An event about a creator alone has account: null.

Missed some? GET /v1/events lists what was sent to the key's webhook, newest first, with each one's delivery status. It has only events from while the key had a webhook URL.

Events

Events name what changed and the IDs involved; fetch the details from the API. Events about a creator also carry creator. Ignore types you don't handle.

To finish a hosted step a creator or brand has to take, send them to the URL from POST /v1/connections (a platform sign-in, payout setup or a card), then wait for creator.payouts_updated or connection.connected.

EventWhendata
application.createdA creator applies to a programprogram, application
application.invitedA creator is invited and must accept the program's agreements (agreementsRequired)program, application, creator
application.approvedAn application is approvedprogram, application
application.rejectedAn application is rejectedprogram, application
application.withdrawnA creator withdraws an applicationprogram, application
agreement.acceptedA creator accepts the account's agreementscreator, program when for one
submission.createdA creator submits workprogram, submission
submission.approvedA submission is approvedprogram, submission
submission.revision_requestedA reviewer asks for changesprogram, submission
submission.rejectedA submission is rejectedprogram, submission
submission.liveA submission goes liveprogram, submission
file.readyAn uploaded file finishes processingfile
file.failedAn uploaded file can't be usedfile
campaign.publishedA campaign goes live on its platformcampaign
campaign.partially_failedA launch succeeds on some platforms and fails on otherscampaign
campaign.failedA launch failscampaign
campaign.pausedA campaign is pausedcampaign
campaign.archivedA campaign is archivedcampaign
campaign.needs_attentionA campaign needs action on its ad platformcampaign
payment.createdA payment to a creator is createdpayment, creator
payment.pending_approvalA payment waits for the brand to approve itpayment, creator
payment.succeededA payment to a creator succeedspayment, creator
payment.failedA payment to a creator failspayment
payment.disputedA payment is disputed during its holdpayment, creator
payment.refundedA payment is refunded in fullpayment
payout.paidEarnings reach a creator's (or this account's) balancepayout
payout.failedSending earnings to a balance failspayout
withdrawal.createdA withdrawal (the account's or a roster creator's) is on its waywithdrawal
withdrawal.paidA withdrawal (the account's or a roster creator's) reached the bankwithdrawal
withdrawal.failedA withdrawal (the account's or a roster creator's) failedwithdrawal
deposit.succeededA deposit settles and its funds are availabledeposit
deposit.failedThe bank debit failed or the Checkout expired unpaid; no funds were addeddeposit
refund.succeededA refund from the balance reaches the card or bankrefund
retainer.acceptedA creator accepts a retainerretainer
retainer.rejectedA creator turns a retainer downretainer
retainer.canceledA retainer is canceledretainer
retainer.endedA retainer endsretainer
sample.createdA creator requests a samplesample
sample.approvedA sample request is approvedsample
sample.rejectedA sample request is rejectedsample
sample.shippedThe store's order for a sample is createdsample, creator
message.createdA message arrives in one of the account's threadsmessage, thread
brand.createdA partner provisions a brandbrand
creator.createdA partner adds a creator to its rostercreator
creator.payouts_updatedA creator's payout setup changes: verified, needs action, or readycreator
connection.connectedA platform account is connectedconnection, creator for a creator's
connection.disconnectedA platform account is disconnectedconnection, creator for a creator's
connection.expiredA platform account needs reconnectingconnection, creator for a creator's
ad_authorization.requestedA brand asks a creator to authorize a submission as an adad_authorization, submission, creator
ad_authorization.grantedThe creator grants itad_authorization, submission, creator
ad_authorization.revokedIt is revokedad_authorization, submission, creator
JSON
{
  "id": "evt_u5s9xZIdD99WBwmDjJPYc3e",
  "type": "submission.approved",
  "account": "acct_04Jm0JWUg20EhYo2lyNIEHo",
  "data": { "submission": "sub_14XA4WGiSOmaVI3B4QLm4Fs" }
}

Verify the signature

Lucra-Signature: t=<seconds>,v1=<hex> is an HMAC-SHA256 of <t>.<raw body> with your secret.

TypeScript
import { createHmac, timingSafeEqual } from "node:crypto"

export function verifyLucraWebhook(
  body: string,
  header: string,
  secret: string
) {
  const { t, v1 } = Object.fromEntries(
    header.split(",").map((part) => part.split("="))
  )
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false
  const expected = createHmac("sha256", secret)
    .update(`${t}.${body}`)
    .digest("hex")
  return (
    v1?.length === expected.length &&
    timingSafeEqual(Buffer.from(v1), Buffer.from(expected))
  )
}

Retries

Answer 2xx within 10 seconds. Failed deliveries retry for 3 days. Events can repeat or arrive out of order, so deduplicate on id.