Guides
Spring Boot integration
A complete walkthrough of integrating confiqure.ai into a Spring Boot 3+ application: add the annotation, mint embed tokens from a service, and receive callbacks into a controller.
1. Add the annotation dependency
Pull in confiqure-annotation-java from Maven Central — no extra repository entry needed.
<dependency>
<groupId>ai.confiqure</groupId>
<artifactId>confiqure-annotation-java</artifactId>
<version>3.0.0</version>
</dependency> The library uses an annotation processor that hooks into javac's internal APIs (same mechanism as Lombok) to inject a backend-assigned confiqureKey field plus getter/setter into each annotated class. Add these compiler args so the processor can run on JDK 11+.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<fork>true</fork>
<compilerArgs>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg>
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED</arg>
</compilerArgs>
</configuration>
</plugin> Without these args, the processor silently skips injection and the confiqureKey round-trip won't work. The -J prefix passes the flag to the forked javac JVM rather than to javac itself, which is why <fork>true</fork> is required.
2. Annotate a configuration class
Pick a class that represents user-tunable settings. Use plain Java types — confiqure handles enums, lists, and nested classes natively.
package com.example.notifications;
import ai.confiqure.Confiqure;
import java.util.List;
@Confiqure.Setting(end = "/notifications")
public class NotificationPreferences {
// Which channels should receive alerts?
private List<Channel> channels;
// How often should digest emails be sent?
private Frequency digestFrequency;
// Getters/setters omitted.
}3. Push the schema
$ git commit -am "add notification config"
$ npx @confiqure/cli push Push targets your sandbox workspace; promote with push --production when the conversation behaves — details on the push page.
4. Mint embed tokens from a service
Store your cqai_… key in application.properties as a secret. A thin RestClient handles the mint. Send the attachments block on every mint: without it the widget shows no attach button, no paste and no ctrl+v.
@Service
public class ConfiqureClient {
private final RestClient http;
private final String workspaceKey;
private final String apiKey;
public ConfiqureClient(
@Value("${confiqure.workspace-key}") String workspaceKey,
@Value("${confiqure.api-key}") String apiKey
) {
this.workspaceKey = workspaceKey;
this.apiKey = apiKey;
this.http = RestClient.builder()
.baseUrl("https://api.confiqure.ai")
.build();
}
public EmbedToken mint(String userId) {
return http.post()
.uri("/api/{k}/embed-tokens", workspaceKey)
.header("Authorization", "Bearer " + apiKey)
.contentType(MediaType.APPLICATION_JSON)
.body(Map.of(
"endUserHandle", userId,
"attachments", Map.of("enabled", true,
"allowedTypes", List.of("image/png", "image/jpeg"),
"camera", true),
"ttlSeconds", 3600
))
.retrieve()
.body(EmbedToken.class);
}
public record EmbedToken(String token, String jti, Instant expiresAt) {}
}5. Expose a "give-me-a-token" endpoint
Your authenticated frontend calls this when it needs to mount the widget. The endpoint must verify the caller and only mint a token for their own endUserHandle — never trust a userId from the request body.
@RestController
@RequestMapping("/api/widget")
public class ConfiqureWidgetController {
private final ConfiqureClient confiqure;
@PostMapping("/notifications/token")
public EmbedToken token(Authentication auth) {
String userId = auth.getName();
return confiqure.mint(userId);
}
}6. Receive the callback
Mark one controller method with @Confiqure.DefaultCallbackHook — its route is discovered on push and combined with the host base URL you set once in Dashboard settings. The payload carries confiqureKeys, not values: on onComplete, fetch each key and bind its data into your class.
@RestController
@RequestMapping("/api/confiqure")
public class ConfiqureCallbackController {
private final NotificationPreferencesService prefs;
private final ObjectMapper json;
@Confiqure.DefaultCallbackHook
@PostMapping("/callback")
public ResponseEntity<Void> receive(@RequestBody CallbackPayload body) {
// One endpoint, every lifecycle event — branch on `event`.
if (!"onComplete".equals(body.event())) {
return ResponseEntity.ok().build(); // onStart / session closes: ack and ignore
}
// onComplete carries confiqureKeys, not values — pull each instance and bind its `data`.
for (String key : body.confiqureKeys()) {
Map<String, Object> data = confiqure.fetchData(body.configEnd(), body.endUserHandle(), key);
prefs.save(body.endUserHandle(), json.convertValue(data, NotificationPreferences.class));
}
return ResponseEntity.ok().build();
}
public record CallbackPayload(
String event,
String workspaceKey,
String configEnd,
String endUserHandle,
Long conversationId,
List<String> confiqureKeys,
String timestamp
) {}
}application.properties
confiqure.workspace-key=abcdef
confiqure.api-key=${CONFIQURE_API_KEY}That's the whole integration
No settings forms, no CRUD endpoints, no client-side validation. Two controllers, one service, one annotated class.