Submit a template version for review
/v1/whatsapp/templates/{template_ref}/versions/{version_id}/submitconst draftId = "wav_01krdgeqcxet5s7t44vh8rt9mg";
const check = await bird.whatsapp.templates.versions.submit("order_update", draftId, {
validate_only: true,
});
for (const problem of check.errors) console.log(problem.language, problem.field, problem.message);
if (check.valid) {
const submitted = await bird.whatsapp.templates.versions.submit("order_update", draftId);
console.log(submitted.version?.id);
}draft_id = "wav_01krdgeqcxet5s7t44vh8rt9mg"
check = client.whatsapp.templates.versions.submit("order_update", draft_id, validate_only=True)
for problem in check.errors:
print(problem.language, problem.field, problem.message)
if check.valid:
submitted = client.whatsapp.templates.versions.submit("order_update", draft_id)
print(submitted.version.id if submitted.version else None)ctx := context.Background()
draftID := "wav_01krdgeqcxet5s7t44vh8rt9mg"
check, err := client.Whatsapp.Templates.Versions.Submit(ctx, "order_update", draftID,
bird.WhatsappTemplatesVersionsSubmitParams{ValidateOnly: bird.Bool(true)})
if err != nil {
log.Fatal(err)
}
if check.Errors != nil {
for _, problem := range *check.Errors {
fmt.Println(*problem.Message)
}
}
if check.Valid != nil && *check.Valid {
submitted, err := client.Whatsapp.Templates.Versions.Submit(ctx, "order_update", draftID,
bird.WhatsappTemplatesVersionsSubmitParams{})
if err != nil {
log.Fatal(err)
}
if submitted.Version != nil {
fmt.Println(submitted.Version.Id)
}
}$draftId = 'wav_01krdgeqcxet5s7t44vh8rt9mg';
$check = $bird->whatsapp->templates->versions->submit('order_update', $draftId, (new WhatsAppTemplateSubmit())->setValidateOnly(true));
foreach ($check->getErrors() ?? [] as $problem) {
echo $problem->getLanguage(), ' ', $problem->getField(), ' ', $problem->getMessage(), "\n";
}
if ($check->getValid()) {
$submitted = $bird->whatsapp->templates->versions->submit('order_update', $draftId, new WhatsAppTemplateSubmit());
echo $submitted->getVersion()?->getId(), "\n";
}bird whatsapp templates versions submit <template-ref> <version-id> \
--validate-only=truecurl -X POST "https://us1.platform.bird.com/v1/whatsapp/templates/{template_ref}/versions/{version_id}/submit" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"validate_only": true
}'{
"errors": [
{
"language": "pt-BR",
"field": "components[2].cards[0].components[1].example_parameters[0].url",
"code": "E15025",
"message": "A body block needs text."
}
],
"version": {
"id": "wav_01krdgeqcxet5s7t44vh8rt9mg",
"template_id": "wat_01krdgeqcxet5s7t44vh8rt9mg",
"version_number": 4,
"submitted_at": "2026-07-20T11:04:00Z",
"languages": {
"en": {
"status": "approved",
"components": [
{
"type": "body",
"text": "Your verification code is {{1}}.",
"example_parameters": [
{
"type": "text",
"text": "123456"
}
]
}
]
}
},
"created_at": "2026-07-20T10:31:00Z"
},
"next": [
{
"kind": "operation"
}
]
}
Validates the template draft before freezing it. If validation fails, the
API returns 200 OK with every problem across all languages and creates no
version. If validation passes, the API freezes an immutable version and
returns 202 Accepted. Only the languages whose content differs from their
approved copy are sent to Meta and marked pending; the rest carry their
existing verdict forward untouched, so a version whose languages are all
unchanged comes back already settled. A language an earlier version carried
but the draft dropped is deleted at Meta in the background. It is absent
from the new version, so it has no pending state. The version becomes live when any
language is approved, and no replacement draft opens automatically.
Set validate_only: true to run the same checks without freezing or
submitting a version. Local validation cannot predict Meta's decision. A
202 Accepted response confirms the version was frozen and accepted. Poll the
version or the template's pending_version_id for the verdicts on whichever
languages it marked pending.
Parameters
template_refstringTemplate ID (wat_ prefix) or slug. A value that parses as a valid ID resolves by ID; any other value resolves as a slug.
version_idstringID of the template version (wav_ prefix), as returned by the version list.
Request Payload
validate_onlyCheck the draft without submitting it. Every language is checked and every problem reported, but nothing is frozen and nothing is sent to WhatsApp. A clean result does not guarantee WhatsApp approval: WhatsApp offers no way to ask in advance, so it can still refuse a submission these checks passed. Use a different idempotency key for the validation and the submission. Reusing one key with different request bodies is rejected.
Response Payload
validWhether the version passed every check.
errorsEvery problem found across every language. One round trip identifies all problems. Empty when valid is true.
versionThe version this submit froze, or null when nothing was frozen: a check run, or a submit that found problems.
nextWhat to do next with this outcome. Present on reads that compute it: an empty list means there is nothing to do, and the field is absent entirely on responses that do not report next actions.
A failed check routes back to the languages that need an edit; a clean one routes to the real submit, and then to polling the template for WhatsApp's verdicts.
Show child attributes
next.kindWhat you do about this step.
operation: call the operation named inoperation, then read again.external: act somewhere this API does not reach, then read again.wait: nothing is asked of you, so read again later.terminal: nothing you do resolves this, so stop retrying.
Tolerate a value you do not recognize: show the description and
offer no action.
Possible values (may grow over time): operation, external, wait, terminal
next.descriptionA short, human-readable label for the step, suitable for display.
next.operationThe operationId to call. Present only when kind is operation. The operation's own schema says how to call it; this says only which one, and what to address it with.
next.paramsThe parameters that address the operation, by name: {"sender_id": "…"} for an operation on /v1/sms/senders/{sender_id}/requirements. A parameter the operation takes in its query string is given the same way, so an operation addressed as ?subject_id= carries {"subject_id": "…"}. Every parameter the call needs is here, whether its value came from the thing you were acting on or is fixed for this step, so you can make the call from this object alone. Present only when kind is operation and the operation names a subject. A request body, when the operation takes one, is described by the operation's own schema and never appears here.
next.urlA URL to open. Present only when kind is external, and only when the step has one. An external step whose description says to go and do something with no URL to open is normal.
Related resources
Continue with the documentation, guides and examples for this topic.