Branding and customization
Put your logo, colors, fonts, terminology, and support links on every Wingspan screen and email your contractors see, through the V3 API.
You set branding in two places:
| Where | Endpoint | What it controls |
|---|---|---|
| Organization customization | GET and PATCH /v3/platform/customization | Company name, logos, the full visual theme, terminology, support links, and email branding. Every Account in your Organization uses it. |
| Account profile | PATCH /v3/platform/accounts/{accountId} | One Account's logo, brand color, and support contact. They appear on that Account's invite page and in the ACH and card authorization terms. |
Organization customization covers the signed-in Wingspan app, embedded onboarding and payout screens, and Wingspan emails. Reading it needs the users.customization:read scope and updating it needs users.customization:write. PATCH follows JSON Merge Patch: fields you leave out keep their current value, and null clears a field. To change the customization of another Account's Organization, send that Account's ID in X-Wingspan-Account.
Brand identity
| Field | What it's for |
|---|---|
companyName | Your company name. Contractors see it where they'd otherwise see Wingspan. |
websiteUrl | Your company website. |
logoUrl | Your main logo. |
secondaryLogoUrl | The logo in the app header. When it's set, the header shows it instead of logoUrl, so use the version that reads best at header height. |
faviconUrl | The browser tab icon on Wingspan-hosted pages. |
Logo and favicon URLs must be public HTTPS URLs. The app header has a light background, so use a dark or full-color logo there. A white (reverse) logo won't show up. If you set assetOrigin to SelfHosted, every URL must be on your own domain.
Visual theme: appearance
appearanceappearance styles every Wingspan-hosted and embedded screen: buttons, links, text fields, checkboxes, dropdowns, and text. Every key is optional. Set the global keys for a quick brand match, then set individual component keys where you need exact control. A component key you set takes priority over the value a global key fills in.
Values are CSS values sent as strings: colors (#1F6FEB, rgba(31, 111, 235, 0.4)), lengths (8px, 1rem), font stacks, shadows, and font weights ("600").
Global keys
| Key | What it sets | Also fills in, unless you set these yourself |
|---|---|---|
colorPrimary | Your primary brand color | button.primaryBg, button.secondaryBorder, button.secondaryColor, link.color, input.borderColorFocus, control.checkedBg, control.checkedBorder, plus the hover, pressed, and focus shades below |
borderRadius | Corner rounding | button.borderRadius, input.borderRadius, select.dropdownBorderRadius |
fontFamily | Font stack | button.fontFamily, input.fontFamily, text.fontFamily, select.searchFontFamily |
colorText | Text color | text.color, control.labelCheckedColor |
colorBorder | Border color | input.borderColor |
Wingspan derives these shades from colorPrimary:
| Key | Shade |
|---|---|
button.primaryBgHover, link.colorHover | 20% darker |
button.primaryBgActive, link.colorActive | 45% darker |
button.focusRingColor, input.focusRingColor, control.focusRingColor | 80% lighter |
button.secondaryActiveBg | 90% lighter |
button.secondaryHoverBg, control.activeBg | 95% lighter |
Send colorPrimary as six-digit hex, such as #1F6FEB, so Wingspan can derive the shades. If you use another color format, set the hover, pressed, and focus keys yourself. The top-level primaryColor field and appearance.colorPrimary are the same setting.
button
button| Key | What it styles |
|---|---|
borderRadius | Button corner rounding |
fontFamily | Button font |
fontWeight | Button font weight |
primaryBg | Primary button background |
primaryBgHover | Primary button background on hover |
primaryBgActive | Primary button background while pressed |
primaryColor | Primary button text |
secondaryBorder | Secondary (outline) button border |
secondaryColor | Secondary button text |
secondaryHoverBg | Secondary button background on hover |
secondaryActiveBg | Secondary button background while pressed |
tertiaryBorder | Tertiary button border |
tertiaryColor | Tertiary button text |
focusRingColor | Focus ring around a button |
disabledBg | Disabled button background |
disabledColor | Disabled button text |
link
link| Key | What it styles |
|---|---|
color | Link color |
colorHover | Link color on hover |
colorActive | Link color while pressed |
input
inputText fields and other form inputs.
| Key | What it styles |
|---|---|
borderRadius | Input corner rounding |
borderColor | Input border |
borderColorFocus | Input border while focused |
bg | Input background |
color | Input text |
placeholderColor | Placeholder text |
fontFamily | Input font |
focusRingColor | Focus ring around an input |
disabledBg | Disabled input background |
disabledColor | Disabled input text |
control
controlCheckboxes, radio buttons, and switches.
| Key | What it styles |
|---|---|
borderColor | Control border |
checkedBg | Background when checked |
checkedBorder | Border when checked |
activeBg | Background while pressed |
focusRingColor | Focus ring around a control |
labelColor | Label text |
labelCheckedColor | Label text when checked |
disabledBg | Disabled control background |
disabledBorder | Disabled control border |
text
texttext.fontFamily and text.color set the font and color for all text. Each text style below takes fontSize, lineHeight, and fontWeight.
| Key | Used for |
|---|---|
display1, display2 | Display text, largest first |
heading1 to heading5 | Headings, largest first |
paragraph | Body text |
paragraphSm | Small body text |
paragraphXs | Extra-small body text |
caption | Captions and helper text |
select
selectDropdown menus.
| Key | What it styles |
|---|---|
dropdownBg | Menu background |
dropdownBorderRadius | Menu corner rounding |
dropdownShadow | Menu shadow |
optionHoverBg | Option background on hover |
optionActiveBg | Background of the selected option |
searchFontFamily | Font in the menu's search box |
Fonts
Wingspan screens load the fonts you declare in fonts. Add your font there from Google Fonts or as a hosted .woff2 file, then use the same family name in appearance. End every font stack with a generic family such as sans-serif. If your font can't load, the text then falls back to a sans-serif font instead of the browser's default serif.
Examples
One color is enough to rebrand buttons, links, focus states, and checkboxes:
curl -X PATCH https://api.wingspan.app/v3/platform/customization \
-H "Authorization: Bearer $WINGSPAN_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{ "appearance": { "colorPrimary": "#1F6FEB" } }'A full brand setup with logos, a custom font, and component overrides:
curl -X PATCH https://api.wingspan.app/v3/platform/customization \
-H "Authorization: Bearer $WINGSPAN_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"companyName": "Northwind Staffing",
"websiteUrl": "https://www.example.com",
"logoUrl": "https://www.example.com/northwind-logo.png",
"secondaryLogoUrl": "https://www.example.com/northwind-wordmark.png",
"faviconUrl": "https://www.example.com/favicon.png",
"fonts": {
"bodyFont": {
"source": "GoogleFonts",
"family": "Inter",
"googleFontsFamily": "Inter",
"fallback": "system-ui, sans-serif"
}
},
"appearance": {
"colorPrimary": "#1F6FEB",
"borderRadius": "8px",
"fontFamily": "Inter, system-ui, sans-serif",
"colorText": "#111827",
"colorBorder": "#E5E7EB",
"button": {
"borderRadius": "6px",
"fontWeight": "600",
"primaryBg": "#1F6FEB",
"primaryBgHover": "#1959BC",
"primaryBgActive": "#113D81",
"primaryColor": "#FFFFFF",
"secondaryBorder": "#1F6FEB",
"secondaryColor": "#1F6FEB",
"secondaryHoverBg": "#F4F8FE",
"secondaryActiveBg": "#E9F1FD",
"focusRingColor": "rgba(31, 111, 235, 0.4)",
"disabledBg": "#E5E7EB",
"disabledColor": "#9CA3AF"
},
"link": {
"color": "#1F6FEB",
"colorHover": "#1959BC",
"colorActive": "#113D81"
},
"input": {
"borderRadius": "6px",
"borderColor": "#D1D5DB",
"borderColorFocus": "#1F6FEB",
"bg": "#FFFFFF",
"color": "#111827",
"placeholderColor": "#9CA3AF",
"focusRingColor": "rgba(31, 111, 235, 0.4)"
},
"control": {
"borderColor": "#D1D5DB",
"checkedBg": "#1F6FEB",
"checkedBorder": "#1F6FEB",
"labelColor": "#374151"
},
"text": {
"color": "#111827",
"heading1": { "fontSize": "2rem", "lineHeight": "2.5rem", "fontWeight": "700" },
"paragraph": { "fontSize": "1rem", "lineHeight": "1.5rem", "fontWeight": "400" }
},
"select": {
"dropdownBg": "#FFFFFF",
"dropdownBorderRadius": "8px",
"dropdownShadow": "0 4px 6px rgba(0, 0, 0, 0.1)",
"optionHoverBg": "#F3F4F6",
"optionActiveBg": "#E5E7EB"
}
}
}'The response returns the full customization, including appearance.
CSS variables
Each appearance key sets a CSS custom property on Wingspan screens. For example, button.primaryBg sets --ws-button-primary-bg, and text.heading1.fontSize sets --ws-text-heading1-font-size. Use appearance for everything it covers, and css.variables or css.stylesheetUrl for styling outside these keys.
Terminology
Rename the words people see in the app. Keys that start with sendPayments change your team's screens. Keys that start with getPaid change what contractors see.
| Key | Default | Where it appears |
|---|---|---|
sendPaymentsContractor | Contractor | Side navigation and page titles. The app pluralizes it, so Provider shows as "Providers". |
sendPaymentsPayable | Payable | Payables navigation, page titles, and tables |
sendPaymentsPaidAt | Processed (paid) | The paid-date column in payables tables |
sendPaymentsOverdueAt | Past Due | The overdue status label |
getPaidClient | Client | The company paying the contractor |
getPaidInvoice | Invoice | Contractor invoices |
curl -X PATCH https://api.wingspan.app/v3/platform/customization \
-H "Authorization: Bearer $WINGSPAN_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"terminology": {
"sendPaymentsContractor": "Provider",
"sendPaymentsPayable": "Work order",
"getPaidClient": "Practice"
}
}'Support links and contacts
| Field | What it does |
|---|---|
supportEmail | Your general support email. |
support.payerSupportEmail | The support email your team members see. |
support.payeeSupportEmail | The support email contractors see. |
support.documentation.generalUrl | Your help center. |
support.documentation.payoutInformationUrl | Your page about how contractors get paid. Contractors see a link to it in their payout settings. |
support.documentation.refundPolicyUrl | Your refund policy. The international bank account form links to it. |
support.portal.generalUrl | Your support portal. When it's set, Support in the app opens your portal in place of Wingspan's support chat. |
curl -X PATCH https://api.wingspan.app/v3/platform/customization \
-H "Authorization: Bearer $WINGSPAN_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"supportEmail": "[email protected]",
"support": {
"payerSupportEmail": "[email protected]",
"payeeSupportEmail": "[email protected]",
"documentation": {
"generalUrl": "https://help.example.com",
"payoutInformationUrl": "https://help.example.com/payouts",
"refundPolicyUrl": "https://www.example.com/refund-policy"
},
"portal": { "generalUrl": "https://support.example.com" }
}
}'Emails
contractorInviteMessage adds your own HTML paragraph inside every contractor invite email. Wingspan removes unsafe markup before sending.
To add a note to one invitation, send up to 2,000 characters of plain text in customMessage on Invite a payee. The message appears in the invitation email Wingspan sends.
curl -X POST https://api.wingspan.app/v3/payments/payees/Py3mQw7kLx2tRb9nVd4sHa/invite \
-H "Authorization: Bearer $WINGSPAN_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{ "customMessage": "Welcome to Northwind Staffing. Set up payments so we can pay you for your first shift." }'Team-member invites (POST /v3/platform/persons/{personId}/invite) accept a message and a redirectUri for where the person lands after signing up.
To send Wingspan emails from your own domain, set emails.ap, emails.ar, or emails.transactional to WingspanManagedBranded with a verified senderDomain. emailTemplates overrides the subject, sender name, preview text, body, and button label of individual emails. See Update Organization branding for every option.
Account profile: logo, brand color, and support contact
Every Account also has a presentation profile. A new payee sees that Account's logo and brand color on the invite page before signing in, and supportEmail appears in the ACH and card authorization terms your customers accept. Update an Account sets these profile fields:
| Field | What it's for |
|---|---|
logoUrl | A public URL for the Account's logo. |
brandColor | The Account's brand color. |
supportEmail | The business support email shown in the ACH and card authorization terms your customers accept. |
supportPhone | A support phone number. |
locale, timezone | The Account's locale and time zone. |
Important:
profileis replaced as a whole. If you sendprofile, send every profile field you want to keep, not only the one you're changing. Read the Account first withGET /v3/platform/accounts/{accountId}, change the field, then send the complete object. Leavingprofileout of the request leaves it unchanged.
curl -X PATCH https://api.wingspan.app/v3/platform/accounts/Ap4tYs8KqW2nLm6xRb1cVe \
-H "Authorization: Bearer $WINGSPAN_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"profile": {
"logoUrl": "https://www.example.com/northwind-logo.png",
"brandColor": "#1F6FEB",
"supportEmail": "[email protected]",
"supportPhone": "+1 555 010 0199",
"locale": "en-US",
"timezone": "America/Chicago"
}
}'Reading and updating an Account needs the users.account:read and users.account:write scopes.
profile is for display only. It can also hold company.links (websiteUrl, linkedInUrl, and other social links). Omitting company keeps its links, and sending company replaces them. Don't set identity there: the legal name, address, and tax ID of record live on the Account's compliance entity. See Tax information.
Moving from V1
V1 customization (PATCH /users/customization/{id}) maps to V3 like this. appearance and terminology keep the same keys.
| V1 field | V3 field |
|---|---|
branding.name | companyName |
branding.url | websiteUrl |
branding.primaryLogoUrl | logoUrl |
branding.secondaryLogoUrl | secondaryLogoUrl |
appearance | appearance |
terminology | terminology |
support.generalSupportEmail | supportEmail |
support.payerSupportEmail, support.payeeSupportEmail, support.documentation, support.portal | Same paths under support |
emailCustomization.templates.contractorInvite.payerMessageSnippet | contractorInviteMessage |
emailCustomization logos, footer, and styles | logoUrl, emailTemplates, and emails |
inheritanceStrategy.organizationAccountConfig | The branding mode on the child's parent link (Overridable, Enforced, or Disabled). By default an Account inherits the branding values it hasn't set from its parent Accounts. See Parent and child Accounts. |
Your own screens
If you want contractors to see only your product, build the screens yourself on the API. See Embed Wingspan in your app for which steps you can collect through the API and which run in Wingspan-hosted views.
Related pages
Updated 24 minutes ago